- 投稿日:2020-06-04T19:18:08+09:00
Xcodeで画面外に描画されたものを見る方法
- 投稿日:2020-06-04T18:36:32+09:00
僕が一ヶ月でiOSアプリをリリースするまでにしたこと
はじめに
この記事ではタイトル通り僕が一ヶ月でiOSアプリをリリースするまでにしたことを書いています。
IDEはXcode、言語はSwift、フレームワークはSwiftUIです。外部ライブラリは使っていません。
参考にしたもの、勉強を継続できたマインド、困ったことなどについて書ければと思います。
文才は皆無、お手柔らかに。SwiftUIのチュートリアルを見た
SwiftUIというのはiOSアプリ開発のための新しいフレームワークのことです。去年発表されたばかりで、どうやら実務ではまだ導入されていないところも多いらしいです。
何かを調べたり学んだりする時に最も良いのはやはり公式の情報です。SwiftUIのチュートリアル何度も読み込み、同じものを作りました。
この時参考にさせていただいたのが超初心者のためのSwiftUIチュートリアル。
Apple公式のチュートリアルは英語でしか書かれていないので、読むのにとても苦労します。noteで丁寧に日本語で訳してくださっているので大変参上になりました。SwiftUIのチュートリアル応用してなんかできないかなーって考えた
とにかくSwiftUI使ってみたい!せっかくならAppStoreで出したい!と思った僕は完成度が高くオープンソースである公式チュートリアル応用して何かできないかな?と考えました。模倣して自分のアイディアを混ぜていくうちにSwiftUIの良さを活かしたアプリができました。
SwiftUIの参考書を買った
エンジニアに限らず初学者特有のとりあえずモノを揃えたくなるアレです。詳細!SwiftUI iPhoneアプリ開発入門ノートとSwiftUI徹底入門を買いました。どちらも大変参考になり、助けられました。詳細!の方は文法などもカバーされていて、かなり初心者向け。しかし、@Stateや@Bindingなどのオプショナルラッパーについてもきちんと書かれている。徹底入門は少し難しめ。僕は辞書がわりに使っていました。
参考書を購入するメリットとしては体系的な知識とともに、機能するソースコードが手に入るです。SwiftUIは新しい技術なので、検索しても期待した結果が得られることは少ないです。「〇〇 SwiftUI」で検索してももしかして Swiftなんて出ることもザラです。
Qiitaに記事を書いてアウトプットした
僕の過去の記事には自己満備忘録がたくさんあります。プログラミングに勉強に限らず人に見られることを意識したアウトプットがめちゃくちゃ大事だと思ったので、とにかく記事を書きました。繰り返しになりますがSwiftUIの技術記事はめちゃ少ないので、僕が記事を残しとこう!みたいな謎の使命感も多少はありました。(たまにLGTMがもらえて嬉しい。)
Teratailで質問した
アプリ開発をきっかけにTeratailデビューもしました。初めての質問はForEachで...使ったらコンパイルエラー出るみたいな質問でした。Jsonファイルの作り方みたいな超初歩的な質問もしました。(今振り返ってみると、結構同じ方が回答やクリップをして下さっている。ありがたい?)アプリを作る中でで8つ質問をして7つ解決内4つ自己解決。質問文を書いているうちに「これ試してないぞ..」みたいなパターンもよくありました。
Youtubeで再現した。
文字での学習も大事ですが、動画での学習は再現性がめちゃくちゃ高いです。何より「動かねーじゃねーか!」みたいなストレスが少ない。ほとんどの動画が英語ですが、そこまで気になりませんでした。
できたもの
宣伝っぽくなってしまい恐縮ではありますが、App StoreのURLと写真を載せます。アプリの機能としてはマンセル色表からRGBが得られます。また色を保存したりカラーコードをコピーできます。(インストールして下さい?)
https://apps.apple.com/jp/app/色検索/id1516435071
- 投稿日:2020-06-04T17:45:49+09:00
イテレータをSwift5で実装する
※この記事は「全デザインパターンをSwift5で実装する」https://qiita.com/satoru_pripara/items/3aa80dab8e80052796c6 の一部です。
The Iterator(イテレータ)
0. イテレータの意義
複数の要素の集合について、全要素に対し順に同様の処理を行うデザインパターンをイテレータという。
Swiftではfor-inループなどで使うことが出来る。
Array型, Set型, Dictionary型などあらかじめ用意されている型はもちろんだが、カスタムタイプもSequenceプロトコルに対応させることでfor-inループで回すことができる。
今回はキューを実装した後、それをSequenceプロトコルに対応させfor-inループで回せるようにする。
1. カスタムタイプ(キュー)の実装
キューとは、複数の要素を扱う型の一種でFirst-in Last-out(最初に追加した要素が最後に取り出される)が特徴の物を言う。
SwiftではUITableViewやUICollectionViewを使うときに、構成要素のセルをキューで管理するようになっているためよく見かけるかもしれない。
まず、自前でキューを実装する。キューの要素(ノード)を定義する必要がある。
Queue.swiftprivate final class Node<T> { //ジェネリクスTを用いて任意の肩を格納できる var key: T? //~中略~ init(_ value: T? = nil) { self.key = value } }キュー内では、複数のノードが連続して連なることになる。そのため、次のノードを参照する変数
next
を用意する。var next: Node?次にキュー本体を実装する。キューの先頭と最後のノードを参照する変数
head
、tail
を定義する。キューに一個も要素がない場合は先頭も最後もないことから、nilを許容するoptional型とする。Queue.swiftpublic final class Queue<T> { fileprivate var head: Node<T>? private var tail: Node<T>? public init(){} //後略新しい要素をキューの一番最後に追加するメソッド
enqueue(_:)
を定義する。キューの中に一つも要素がなく、最初の要素を追加する時は少し処理が違うため注意する。Queue.swiftpublic func enqueue(_ value: T) { //渡された値を元にノード(要素)を生成 let newNode = Node<T>(value) guard let _ = self.head else { //キューに要素が一つもない場合(一つ目の要素を追加する場合)の処理 self.head = newNode self.tail = self.head return } //キューにすでに要素がある場合(二つ目以降の要素を追加する場合)の処理 self.tail?.next = newNode self.tail = newNode }デキュー(先頭の要素をキューの中から取り出す)関数を定義する。
Queue.swiftpublic func dequeue() -> T? { //先頭にものがない(キューに要素が一つもない)場合はnilを返す guard let headItem = self.head?.key else { return nil } if let nextNode = self.head?.next { //二つ目の要素がある場合は、これを新しい先頭の要素とする self.head? = nextNode } else { //二つ目の要素がない場合は、デキューにより一つも //要素がなくなることに注意 self.head = nil self.tail = nil } //先頭の要素を返す return headItem }その他標準的な機能を実装する。
empty() -> Bool
: キューが空(要素数がゼロ)かどうかを判定する関数
peak() -> T
: キューの先頭の要素を返す(デキューはしない)Queue.swiftpublic func isEmpty() -> Bool { return head == nil } public func peek() -> T? { return self.head?.key }しかし、これだけではキューをfor-inループで回すことはできない。下記エラー文にあるように、キューを
Sequence
プロトコルに準拠させる必要がある。Queue.playgroundvar queue = Queue<Int>() queue.enqueue(1) queue.enqueue(2) for item in queue {//error: type 'Queue<Int>' does not conform to protocol 'Sequence' print(item) }2.
Sequence
プロトコルへの準拠
Sequence
プロトコルに準拠させるにはQueueIteratorという型をタイプエイリアスとして定義し、関数makeIterator()
でその型を返す必要がある。Queue.swiftextension Queue: Sequence { public typealias Iterator = QueueIterator<T> public func makeIterator() -> Queue<T>.Iterator { return QueueIterator(self) } }
QueueIterator
は、また別のプロトコルIterator
に準拠している必要がある。重要なのは
next()
関数である。この関数は、for-inループで回した時に、ある要素の処理が終わった後の次の要素を返す関数となっている。
「次の要素」が何か? という事を定義するには現在の要素が何か?という点を管理する必要がある。そのため
currentNode
という変数を用意し、next()
関数が呼ばれたらキュー上の次の要素を返す実装とする。Queue.swiftpublic struct QueueIterator<T>: IteratorProtocol { public typealias Element = T private let queue: Queue<T> private var currentNode: Node<T>? init(_ queue: Queue<T>) { self.queue = queue self.currentNode = queue.head } mutating public func next() -> QueueIterator<T>.Element? { guard let node = self.currentNode else {return nil} let nextKey = node.key self.currentNode = node.next return nextKey } }これでキューが
Sequence
プロトコルに準拠した。for-inループの中で使えるようになる。Queue.playgroundvar queue = Queue<Int>() queue.enqueue(1) queue.enqueue(2) for item in queue { print(item)//1 2 }https://github.com/Satoru-PriChan/QueueIteratorDemo
参考文献: https://www.amazon.com/Design-Patterns-Swift-implement-Improve-ebook/dp/B07MDD3FQJ
- 投稿日:2020-06-04T11:28:14+09:00
UIImageからGIFを生成する
個人開発アプリTweetFly
複数枚の画像をGIFに変換する機能がありまして、ちょっと調べてまとめました。下のGIFは
String
->UIImage
->GIF
に変換したサンプルです。
import UIKit import ImageIO import MobileCoreServices class GIFCreator { struct GIFFrame { var image: UIImage var duration: Float } static func create(with frames: [GIFFrame], resizeTo size: CGSize? = nil, filename: String = "temp.gif", completion: @escaping (URL?) -> Void) { DispatchQueue.global(qos: .userInteractive).async { // Resize images if needed let resizedFrames: [GIFFrame] if let size = size { let format = UIGraphicsImageRendererFormat() format.scale = 1 resizedFrames = frames.map { f in let resizedImage = UIGraphicsImageRenderer(size: size, format: format).image { _ in f.image.draw(in: CGRect(origin: .zero, size: size)) } return GIFFrame(image: resizedImage, duration: f.duration) } } else { resizedFrames = frames } let cacheUrl = try! FileManager.default.url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: true) let url = cacheUrl.appendingPathComponent(filename) try? FileManager.default.removeItem(at: url) let cfURL = url as CFURL if let destination = CGImageDestinationCreateWithURL(cfURL, kUTTypeGIF, resizedFrames.count, nil) { let fileProperties = [kCGImagePropertyGIFDictionary as String: [kCGImagePropertyGIFLoopCount as String: 0]] CGImageDestinationSetProperties(destination, fileProperties as CFDictionary?) for frame in resizedFrames { let gifProperties = [kCGImagePropertyGIFDictionary as String: [kCGImagePropertyGIFDelayTime as String: frame.duration]] CGImageDestinationAddImage(destination, frame.image.cgImage!, gifProperties as CFDictionary?) } DispatchQueue.main.async { CGImageDestinationFinalize(destination) ? completion(url) : completion(nil) } } else { DispatchQueue.main.async { completion(nil) } } } } static func create(with images: [UIImage], perFrameDuration: Float = 0.1, resizeTo size: CGSize? = nil, filename: String = "temp.gif", completion: @escaping (URL?) -> Void) { GIFCreator.create(with: images.map { GIFFrame(image: $0, duration: perFrameDuration)}, resizeTo: size, filename: filename, completion: completion) } }使い方は
1、各画像の表示時間が固定の場合は[UIImage]
をGIFCreator
に渡せばOKです。let images = ... GIFCreator.create(with: images) { url in guard let url = url, let data = try? Data(contentsOf: url) else { return } let gif = UIImage(data: data) }2、各画像の表示時間が個別で設定したい場合
let gifFrames: [GIFCreator.GIFFrame] = [ GIFCreator.GIFFrame(image: UIImage(), duration: 0.1), GIFCreator.GIFFrame(image: UIImage(), duration: 0.2), GIFCreator.GIFFrame(image: UIImage(), duration: 0.3) ] GIFCreator.create(with: gifFrames) { url in guard let url = url, let data = try? Data(contentsOf: url) else { return } let gif = UIImage(data: data) }
- 投稿日:2020-06-04T10:00:16+09:00
【Swift】複数行の標準入力の値を取得する
Swiftでpaizaの問題を解くにあたって複数行の標準入力を取得しなければならない場面があり、ググりまくったものの
var input_lines = [String]() let row = 3 for i in 0..<row { input_lines.append(readLine()!) } print(input_lines)こんなような決め打ちのコードしか出てこなかったので初心者なりに考えてコード書いてみました。
本題
var lines = [String]();//文字列でない場合はここの方を変える while true { let str:String? = readLine()//ここも文字列でない場合は型を変える if let str = str{ lines.append(str) }else{ break } } print(lines)このコードであれば、例えば
Hello Worldのような2行の標準入力でも3行でも100行でも対応できます。
ただし
Hello World This is a penこのような一行に複数の値が存在する場合は
str.components(separatedBy: " ")を使うなどして文字列を分割して配列にappendすることをお勧めします。
- 投稿日:2020-06-04T01:19:22+09:00
【プログラミング初心者】Swift基礎~for文・while文~
はじめに
今回はプログラムの基本である繰り返しの方法について紹介します。
多少書き方の違いはありますがSwift以外の多言語全てで使用されるプログラムの基本構文の1つです。
この投稿で書き方をしっかりと覚えておいてください。繰り返しとは
プログラムでは同じ処理を何度も繰り返して実行することが多くあります。
そんなときに繰り返し処理であるfor
やwhile
を使います。例えば簡単な例でいくと、あるStringを格納した配列が定義されており、その内容全てを表示したいとします。
繰り返しを使わなければ以下のようになります。var fruitsList = ["りんご", "みかん", "スイカ"] print(fruitsList[0]) // りんご print(fruitsList[1]) // みかん print(fruitsList[2]) // スイカこの配列の要素が100個になると100回
print()
を記述しなければなりません。またプログラムの実行中に
stringList
に要素が追加された場合、その追加された要素に対して処理することができません。そんなときに繰り返し制御を行って解決します。
for文
for
の構文は以下となります。for 定数 in 配列 { 繰り返す処理 }
for
文は上記の書き方をすると配列の0番目から要素が取り出され、定数に格納されます。
取り出された後処理ブロック内に書かれた処理を実行します。
配列の最後まで要素を取り出し処理が完了すると繰り返しが終了します。最初の例を
for
を使って書き直してみます。var fruitsList = ["りんご", "みかん", "スイカ"] for fruits in fruitsList { print(fruits) }実行結果りんご みかん スイカまた直接配列のインデックスを指定しているわけではないので、要素が追加されても対応できます。
var fruitsList = ["りんご", "みかん", "スイカ"] for fruits in fruitsList { print(fruits) } print("--- 要素を追加 ---") fruitsList.append("パイナップル") fruitsList.append("いちご") for fruits in fruitsList { print(fruits) }実行結果りんご みかん スイカ --- 要素を追加 --- りんご みかん スイカ パイナップル いちご指定回数繰り返す
次の例は指定回数処理を繰り返す方法です。
※多言語のようなインデックスをインクリメントする方法はありません。for index in 1...10 { print("index:\(index)") }実行結果index:1 index:2 index:3 index:4 index:5 index:6 index:7 index:8 index:9 index:10実はこの処理は先程説明した要素を取り出す方法と同じです。
今回配列部分に1...10
と指定しました。
これは1~10のInt
型配列を作る書き方です。
そのため作られた10要素を持つ配列から1つずつ要素が取り出されたというわけです。
(正確には配列ではなくRange<Int>
という型です)辞書型をfor文で処理する
辞書型も配列と同様に
for
文で処理できます。let fruitsList = ["apple": "りんご", "mandarin_orange": "みかん", "watermelon": "スイカ"] for (key, value) in fruitsList { print("key: " + key) print("value: " + value) print("---") }実行結果key: watermelon value: スイカ --- key: mandarin_orange value: みかん --- key: apple value: りんご ---辞書型の場合、
(key, value)
のように要素を取り出しています。
これはTuple
型といい、今回詳しくは説明しませんが複数の値を同時に扱う方法です。
メソッドのない構造体のようなイメージです。辞書型の場合
Tuple
として取り出すことができ、第一要素にキー、第二要素に値が入ってきます。
また(key, value)
とわかりやすいように定義していますが、この変数名は自由に決めることができ、(a, b)
というように取り出しても問題ありません。
重要なのは第一要素にキー、第二要素に値ということだけです。while文
次は
while
文を紹介します。
while
の構文は以下となります。while 継続条件 { 繰り返す処理 }継続条件は
if
文のようにBool
型で与える必要があります。
継続条件がfalse
となったタイミングで処理を抜けます。以下の例は整数を1ずつ足していき、合計が10になったら繰り返しを終了する処理です。
var sum = 0 while sum < 10 { sum += 1 print("合計: \(sum)") }実行結果合計: 1 合計: 2 合計: 3 合計: 4 合計: 5 合計: 6 合計: 7 合計: 8 合計: 9 合計: 10気をつけたいことは今回の例では発生することはあり得ませんが、条件文が常に
true
になってしまうと無限ループが発生してしまいます。
無限ループが発生するとそれ以降の処理は一切実行されず、アプリはフリーズしてしまいます。簡単に無限ループさせるには以下のようにします。
※実行すると永遠に処理が走るので実行はおすすめしません。実行したとしても適度なタイミングでプログラムを停止してください。while true { sum += 1 print("合計: \(sum)") }継続条件に固定値として
true
が入っているので処理を抜けることはありません。これは単純な例ですが、複雑な条件や処理になると発生してしまうので注意してください。
処理を途中で抜ける
繰り返し処理を行っている中で途中で処理を抜けたいということがたまに発生します。
例えば検索処理などです。
配列の中から見つけたい要素があったら変数に格納し以降の処理は行わない、というように実装したいとします。
その場合break
というものを使い処理ブロックを抜けます。
以下が具体的な実装です。let fruitsList = ["りんご", "パイナップル", "いちご", "オレンジ", "スイカ"] var orange = "" for fruits in fruitsList { if fruits == "オレンジ" { orange = fruits break // ここでforの処理を抜ける } print("fruits: \(fruits)") } print("検索結果: \(orange)")実行結果fruits: りんご fruits: パイナップル fruits: いちご 検索結果: オレンジ実行結果を見ると
print("fruits: \(fruits)")
が「オレンジ」と「スイカ」では実行されていません。
fruits
の値が「オレンジ」になったときにif
の処理ブロックに入り、break
でfor
の処理ブロックを抜けたためです。
while
でも同様にbreak
を実行すると処理ブロックから抜けることができます。forとwhileの違い
繰り返し処理という意味ではこの2つに違いはありません。
違いとしてはfor
は配列など数が決まっているものに対して行う有限の繰り返しに対し、while
は終了条件を満たすまで繰り返される処理ということです。C言語などではよく使いますが、Swiftではあまり
while
文は見かけません。
ですがが、処理を待ち受けたいときなど使う場面もあります。
通信処理などでたまに使うイメージです。どちらも使えるようにしておきたいところです。
最後に
今回はSwiftで繰り返し処理を行う方法を紹介しました。
繰り返し処理は条件分岐処理と同様に重要でプログラミングの基礎となる内容なのでこの機会にしっかりと押さえておきましょう。今回の内容は以上です。
本記事とは別でプログラミング未経験からiOSアプリ開発が行えるようになることを目的とした記事を連載しています。
連載は以下にまとめていますのでそちらも是非もご覧ください。
アジェンダ:https://qiita.com/euJcIKfcqwnzDui/items/0b480e96166e88945684