20200604のSwiftに関する記事は6件です。

Xcodeで画面外に描画されたものを見る方法

Debug View Hierarchyでデバッグするとき、画面外に描画されて見えないことがあります。

下の丸で囲ってるところをクリックすると、画面外に描画されてるものが見えるようになります。
スクリーンショット 2020-06-04 17.54.20.png

便利!!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

僕が一ヶ月で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

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

イテレータを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.swift
private final class Node<T> {

    //ジェネリクスTを用いて任意の肩を格納できる
    var key: T?    

    //~中略~

    init(_ value: T? = nil) {
        self.key = value
    }
}

キュー内では、複数のノードが連続して連なることになる。そのため、次のノードを参照する変数nextを用意する。

    var next: Node?

次にキュー本体を実装する。キューの先頭と最後のノードを参照する変数headtailを定義する。キューに一個も要素がない場合は先頭も最後もないことから、nilを許容するoptional型とする。

Queue.swift
public final class Queue<T> {
    fileprivate var head: Node<T>?
    private var tail: Node<T>?

    public init(){}
//後略

新しい要素をキューの一番最後に追加するメソッドenqueue(_:)を定義する。キューの中に一つも要素がなく、最初の要素を追加する時は少し処理が違うため注意する。

Queue.swift
    public 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.swift
    public 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.swift
    public func isEmpty() -> Bool {
        return head == nil
    }

    public func peek() -> T? {
        return self.head?.key
    }

しかし、これだけではキューをfor-inループで回すことはできない。下記エラー文にあるように、キューをSequenceプロトコルに準拠させる必要がある。

Queue.playground
var 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.swift
extension Queue: Sequence {

    public typealias Iterator = QueueIterator<T>

    public func makeIterator() -> Queue<T>.Iterator {
        return QueueIterator(self)
    }
}

QueueIteratorは、また別のプロトコルIteratorに準拠している必要がある。

重要なのはnext()関数である。

この関数は、for-inループで回した時に、ある要素の処理が終わった後の次の要素を返す関数となっている。

「次の要素」が何か? という事を定義するには現在の要素が何か?という点を管理する必要がある。そのためcurrentNodeという変数を用意し、next()関数が呼ばれたらキュー上の次の要素を返す実装とする。

Queue.swift
public 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.playground
var 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

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

UIImageからGIFを生成する

個人開発アプリTweetFly
複数枚の画像をGIFに変換する機能がありまして、ちょっと調べてまとめました。

下のGIFはString -> UIImage -> GIFに変換したサンプルです。
sample_typewritter.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)
}

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【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することをお勧めします。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【プログラミング初心者】Swift基礎~for文・while文~

はじめに

今回はプログラムの基本である繰り返しの方法について紹介します。
多少書き方の違いはありますがSwift以外の多言語全てで使用されるプログラムの基本構文の1つです。
この投稿で書き方をしっかりと覚えておいてください。

繰り返しとは

プログラムでは同じ処理を何度も繰り返して実行することが多くあります。
そんなときに繰り返し処理であるforwhileを使います。

例えば簡単な例でいくと、ある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の処理ブロックに入り、breakforの処理ブロックを抜けたためです。

whileでも同様にbreakを実行すると処理ブロックから抜けることができます。

forとwhileの違い

繰り返し処理という意味ではこの2つに違いはありません。
違いとしてはforは配列など数が決まっているものに対して行う有限の繰り返しに対し、whileは終了条件を満たすまで繰り返される処理ということです。

C言語などではよく使いますが、Swiftではあまりwhile文は見かけません。
ですがが、処理を待ち受けたいときなど使う場面もあります。
通信処理などでたまに使うイメージです。

どちらも使えるようにしておきたいところです。

最後に

今回はSwiftで繰り返し処理を行う方法を紹介しました。
繰り返し処理は条件分岐処理と同様に重要でプログラミングの基礎となる内容なのでこの機会にしっかりと押さえておきましょう。

今回の内容は以上です。
本記事とは別でプログラミング未経験からiOSアプリ開発が行えるようになることを目的とした記事を連載しています。
連載は以下にまとめていますのでそちらも是非もご覧ください。
アジェンダ:https://qiita.com/euJcIKfcqwnzDui/items/0b480e96166e88945684

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む