SwiftUI 個人メモ

SwiftUIの情報をまとめました.
正直キャッチアップはめんどくさいですが,切り捨てられない程度に時代について行きます.
ある程度時代の方向性が定まってから,最短ルートでついて行くつもりですが,SwiftUIに関する仕様の変更点は,まだまだあるのかもしれません.

– 概要の把握には以下の記事が参考になります.
SwiftUIの考察

– コードのサンプルには以下の記事が参考になります.
Apple Developer – SwiftUI Tutorials
Introducing SwiftUI
Fucking SwiftUI
カピ通信 SwiftUI

– SwiftUIサンプルコードを見ていると見慣れない some というキーワードが散見されますが,Swift 5.1 で導入される Opaque Result Type は以下の記事が参考になります.
Swift 5.1 に導入される Opaque Result Type とは何か

– 実際のアプリにSwiftUIを用いる際には,一部のビューから導入するというのが現実だと思われます.
一部の画面だけSwiftUIを使いたいとき

– Xcode11.4を触っていて見慣れない SceneDelegate.swift というのがあったので,調べます.
SwiftUIを触って分かったこと:①初期画面の設定方法

その他,見たことがなかった属性についてもまとめました.
@State
– SwiftUIのViewはstructのため,通常の値を更新することができない.@Stateを宣言することで,メモリの管理がSwiftUIフレームワークに委譲されて値を更新することができる.
– @Stateで宣言されたプロパティを子Viewに渡す時は、プロパティ名の頭に$をつける.プロパティの値そのものではなく、プロパティへの参照を渡すイメージ.
– @Stateは,値をコピーする.

@ObservedObject
– データクラスに対しては,@ObservedObjectを使用する.
– @ObsevedObjectは,View の外部からデータを取得する際に利用する.
– @ObsevedObject に@Published 属性を持つプロパティを用意する場合、そのプロパティに変化があればそのプロパティを利用する View が更新される.

@Binding
【SwiftUI】@Stateとか@Bindingて何

– @BindingをつけてやるとView間での双方向のデータ共有が可能となる.
– 子View側では、@Stateではなく@Bindingでプロパティを宣言する.
– 自らは保有せずに、親のプロパティを参照する.
– @Binding は実体を保持せず、参照してデータソースの値を変更する.
– 利用するデータソースを複数にせず、一つの正しいデータソースのみを参照し変更することで不具合を避けることができる.

@Environment
– @EnvironmentObjectを付与したプロパティは複数のViewで共通のインスタンスを参照する.(アプリ全体で共通のプロパティ)

@ViewBuilder
SwiftUIのViewBuilderについて調べてみる

Swift 3 クロージャーについて

クロージャーについて少し知識が整理できていなかったので,メモしておきます.
なんとなく使えるけど,一からクロージャーを作るといろいろ疑問がでてきたので自分用にまとめてみました.
後になって考えると最初にこれを知っておけば理解が早かったのにということを忘れないようにメモしました.

▼ クロージャーの文法

1
2
3
{ (引数) -> 返値の型 in
	処理
}

▼ クロージャーを理解するときに知っておくと良いこと

– Swift クロージャーは,引数と返値の型を推論してくれる.
– 引数の()が省略できる.
– 返値を省略できる.
– 返値を暗黙に返してくれる.(クロージャ処理が1行の場合,return も省略できる)
– 引数も略式でかける.($0, $1, $2 というように記述できる)
– 最後の引数がクロージャーの場合,外に配置できる.(()内の引数から外に置ける.Trailing Closures)

– [weak self]
– @escaping

▼ 上記のサンプルコード

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 文法どおりのクロージャー
let closureA = {(x: String) -> String in
    return x + "closureAの処理"
}
print(closureA("closureA:")) // closureA:closureAの処理
 
// 暗黙を利用した省略記法のクロージャー
let closureB = { x in
    x + "closureBの処理"
}
print(closureB("closureB:")) // closureB:closureBの処理
 
// クロージャーをパラメータに渡す定義
// (第2引数のクロージャーに第1引数の文字を渡す関数)
func closureParamA(_ x: String, _ y:(String) -> String) -> String {
    return y(x)
}
 
// 最後のパラメーターにクロージャーを渡す
// (既存のクロージャーにパラメータを渡す関数が書ける)
let param1 = closureParamA("closureParamA1:", closureA)
print(param1) // closureParamA1:closureAの処理
 
// 最後のパラメーターのクロージャーを()の外に配置する
// (新たにクロージャーにパラメータを渡す関数が書ける)
let param2 = closureParamA("closureParamA2:") { x in
    x + "closureParamの処理"
}
print(param2) // closureParamA2:closureParamの処理
 
// クロージャー定義をパラメータに書くのが大変なので,typealias を使う
typealias closureType = (String) -> String
func closureParamB(_ x: String, _ y:closureType) -> String {
    return y(x)
}
 
let param3 = closureParamB("closureParamB:", closureA)
print(param3) // closureParamB:closureAの処理

Swift3, APIKit, ObjectMapper, SnapKit を使って Web API にアクセスしてみた件

最近は,Swift3 で iOS アプリを開発しているのですが,改めて使用しているライブラリについて調べてみた.
仕事では日々実装しなくてはいけないテーマがあり,なかなか全体を俯瞰する機会がないので,GW を期に確認してみたというところです.
ネット上で公開されているライブラリに関する記事をざっとチェックしましたが,公開から1年以上経っていることもあり,なかなかそのままのコードで動作させることが難しいようだったので,個人的なメモとして簡単なコードを書くことにしました.

では早速,サンプルで使用する Web API を選びます.

1. Web API を選ぶ

▼ お天気Webサービス仕様
http://weather.livedoor.com/weather_hacks/webservice
ライブドアで無料提供している Web API のようです.
以下のように「地域ID」を指定することで天気情報を取得できるようです.

(例)「福岡県・久留米の天気」を取得する場合は,以下のURLにアクセスしてJSONデータを取得できるとのこと.
http://weather.livedoor.com/forecast/webservice/json/v1?city=400040

簡単そうなので,これにします.

2. APIKit でリクエスト

Xcode 8.3.2 で Swift3 を使用し,シングルページアプリを作成します.
CocoaPods で,APIKit / ObjectMapper / SnapKit を導入します.

APIKit
https://github.com/ishkawa/APIKit
- iOS 8.0 以降に対応.

ObjectMapper
https://github.com/Hearst-DD/ObjectMapper

SnapKit
http://cocoadocs.org/docsets/SnapKit/0.10.0/index.html
https://github.com/SnapKit/SnapKit

Xcode で作成された ViewController.swift に Web API リクエストのコードを記述します.

1
2
3
4
5
6
7
8
9
10
11
12
private func sendWeatherAPI() {
    let request = WeatherRequest(city: 130010)
    Session.send(request) { [weak self] result in
        switch result {
        case .success(let response):
            self?.dispWetherResponse(dto: response)	// ビューに取得データを表示する
 
        case .failure(let error):
            print("error: \(error)")
        }
    }
}

API リクエストは WeatherRequest という名前にしました.
http://weather.livedoor.com/forecast/webservice/json/v1?city=400040 にアクセするするのでこんな感じでしょう.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import Foundation
import APIKit
import ObjectMapper
 
struct WeatherRequest: Request {
    typealias Response = WeatherResponse
    let city: Int
 
    init(city: Int) {
        self.city = city
    }
    var baseURL: URL {
        return URL(string: "http://weather.livedoor.com/forecast/webservice/json")!
    }
    var method: HTTPMethod {
        return .get
    }
    var path: String {
        return "v1"
    }
    var parameters: Any? {
        return nil
    }
    var queryParameters: [String: Any]? {
        return ["city": city.description]
    }
    func response(from object: Any, urlResponse: HTTPURLResponse) throws -> Response {
        guard let object = Mapper<Response>().map(JSONObject: object) else {
            return Response()
        }
        return object
    }
}

3. 取得したデータをマッピング

WebAPI で取得したデータが
func response(from object: Any, urlResponse: HTTPURLResponse) throws -> Response {
に渡されますので,struct にマッピングします.
また,ライブラリのバージョンがあがり,返却するデータの方がオプショナルでなくなっているようです.

とりあえず,3つのデータだけ格納することにします.
階層が一つ下の項目にも map[“description.text”] のようにで参照できるのが便利ですね.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import Foundation
import ObjectMapper
 
struct WeatherResponse: Mappable {
    var publicTime = ""
    var title = ""
    var description = ""
 
    init() {
    }
    init?(map: Map) {
    }
 
    mutating func mapping(map: Map) {
        self.publicTime <- map["publicTime"]
        self.title <- map["title"]
        self.description <- map["description.text"]
    }
}

WeatherResponse が,ViewController.swift の Web API リクエスト部分のクロージャー記述に返されるので,画面に表示しておしまいです.

4. SnapKit で制約を指定する

あと,SnapKit で表示用ラベル位置を調整してみます.
SnapKit で制約を指定するわけですが,指定前にビューに対してラベルを addSubview() しておかなくてはなりません.

制約の指定は以下のようにしました.
一度,Autolayout の制約条件を Xcode から行いましたが,大変だった記憶があり,以下のコードだけで制約を指定できるのは大変便利かと思います.

1
2
3
4
5
6
7
8
9
10
titleLabel.snp.makeConstraints { make in
    make.top.equalTo(20)
    make.left.right.equalTo(view).inset(10)
    make.height.equalTo(30)
}
publicTimeLabel.snp.makeConstraints { make in
    make.top.equalTo(titleLabel.snp.bottom)
    make.left.right.equalTo(view).inset(10)
    make.height.equalTo(30)
}

titleLabel の下に publicTimeLabel が配置されるように制約を指定しています.
また,それぞれの高さは 30 です.

簡単にライブラリを使った検証でした.

▼ 追記

Himotoki っていうライブラリも結構紹介されているなぁーー
https://github.com/ikesyo/Himotoki

Swift3.0 正規表現 について

過去の Swift バージョンで記述したコードが Swift3.0 でコンパイルした際に警告を出力していたので書き直した.

簡単に個人的なメモ
– Swift は正規表現は,NSRange などを使用する必要があるため少し面倒.
– 正規表現を扱うには,NSRegularExpression インスタンスを使用する.
– 正規表現の検索結果を参照する必要がある場合,regex.matches を参照する.
– String クラスの characters.count は文字列の文字を,全角半角関係なく1文字と数える.
– NSString クラスの length は,全角文字を半角文字2文字分として数える.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import Foundation
extension String {
    var count: Int {
        let string_NS = self as NSString
        return string_NS.length
    }
 
    // 正規表現で検索する.
    // .caseInsensitive 大文字小文字の区別を無視する
    func pregMatche(pattern: String, options: NSRegularExpression.Options = [.caseInsensitive]) -> Bool {
        guard let regex = try? NSRegularExpression(pattern: pattern, options: options) else {
            return false
        }
        let matches = regex.matches(in: self, options: [], range: NSMakeRange(0, self.count))
        return matches.count > 0
    }
 
    // 正規表現で検索する.(検索結果を使用)
    func pregMatche(pattern: String, options: NSRegularExpression.Options = [.caseInsensitive], matches: inout [String]) -> Bool {
        guard let regex = try? NSRegularExpression(pattern: pattern, options: options) else {
            return false
        }
        let targetStringRange = NSRange(location: 0, length: self.count)
        let results = regex.matches(in: self, options: [], range: targetStringRange)
        for i in 0 ..< results.count {
            for j in 0 ..< results[i].numberOfRanges {
                let range = results[i].rangeAt(j)
                matches.append((self as NSString).substring(with: range))
            }
        }
        return results.count > 0
    }
}

2016年 iOS 開発手法,使用しているライブラリなどまとめ

年末なので,聞いたり,自分が経験した iOS 開発手法についてまとめておこうと思います.

個人的にはようやく今年の後半になって Swift3 のみで書かれたアプリ開発に参加しました.
以下のライブラリ,ツールの選定についても Swift3 を考慮した選定となっていると思います.
また,開発手法に関してもオブジェクトの配置は,コードベースの AutoLayout を採用したりと変化がありました.
来年もさらに,デバイスのスクリーン サイズが増えそうなので,AutoLayout まわりの知識はより必要となっていくと思われます.
以前,AutoLayout についてざっとチェックした際には,InterfaceBuilder での作業が繁雑であること.また,コードベースの AutoLayout は馴染みのない記載方法だと感じ,あまりいい印象はなかったのですが,最近では AutoLayout + ライブラリ(snapkit など)の利用でレイアウト制約を記述する負担を軽減する手法も紹介されつつあるようです.
今後の技術トレンドとしては,プロトコル指向についてウォッチしていきたいなと考えています.

▼ 開発ツール,ライブラリ

Xcode これがないと始まらない標準 IDE
CocoaPods ライブラリ管理
Carthage ライブラリ管理
Bundler CocoaPodsのバージョンを管理.
SwiftLint コードの品質チェックツール
Swimat Swift の Formatter
mergepbx pbxファイルのマージ用
SnapKit AutoLayout を DSL 風に記述できる.
R.swift Storyboard名や画像名をプロパティーとして取得できる.
Alamofire 通信ライブラリ
ObjectMapper
AlamofireObjectMapper
Kingfisher 画像ダウンロード
XCGLogger ロガー
KeychainAccess Keychainを簡単に使うライブラリ
RealmSwift データベース

▼ SwiftLint

https://github.com/realm/SwiftLint
インストール方法
brew install swiftlint

SwiftLintを試してみた

▼ mergepbx

Xcode プロジェクトの構成情報 pbxproj ファイルを Git でマージするときに手助けしてくれるツール.

インストール方法
$ git clone https://github.com/simonwagner/mergepbx
$ cd mergepbx/
$ ./build.py
$ sudo cp mergepbx /usr/local/bin/

mergepbxを使ってみる

▼ SnapKit

https://github.com/SnapKit/SnapKit#installation
http://snapkit.io/docs/

▼ R.swift

StoryboardからViewControllerを取得する
画像リソースを取得する
フォントやLocalizable Stringを取得する

さらに便利になったR.swiftの実力を見るがいい

R.swiftを使ってStoryboard名や画像名のTypoを0にする

▼ Kingfisher

KingFisher は,SDWebImageを参考にSwiftで実装されていそうです.

[Swift]KingFisherで画像URLから画像データをダウンロード

▼ KeychainAccess

SwiftでKeychainを簡単に使うライブラリ “KeychainAccess” を書きました

▼ RealmSwift

公式ドキュメント
https://realm.io/jp/docs/swift/latest/

RealmSwift入門編

Realm Swiftのまとめ

▼ 参考:

妄想iOSアプリ新規開発

iOSアプリケーションでコードベースのレイアウトを積極利用する

▼ 今後,チェックしたい技術トレンドなど

オブジェクト指向とプロトコル指向
画面遷移(ルーティング)

Swift で Objective-C のカテゴリを実装する方法

既存の Objective-C のコードを Swift 化していて Objective-C のカテゴリって Swift でどうするの?と調べてみると以下のような見解が紹介されていました.

Swiftで継承とカテゴリってどうやってやるの!?

Swift で UIView を角丸にするカテゴリ メソッドを追加するには,以下のように記述すれば実装できるようです.

1
2
3
4
5
6
7
extension UIView {
    // 角丸にする.
    func cornerRadiusClippedToBounds() {
        self.layer.cornerRadius = 5
        self.clipsToBounds = true
    }
}

では,上記のコードのファイル名はどうすべきなのか?というのは… 結局,Objective-C と同じ流儀が紹介されていました.

Swift で Extension につけるファイル名のベストプラクティス

Swift と Objective-C との連携方法

Objective-C で定義したクラスを Swift で使用する

【ブリッジヘッダーファイルを追加する】

ブリッジヘッダーファイルは,$(TARGET_NAME)-Bridging-Header.h で,プロジェクトに追加する.

Swift で利用する Objective-C クラスをブリッジヘッダーファイルに記載する.

【Build Settings ー Objective-C Bridging Header を設定する】
ブリッジヘッダーファイルが Swift に取り込まれるように、ターゲット – Build Settings 設定に登録する.

Swift Compiler - Code Generation
Objective-C Bridging Header
$(TARGET_NAME)/$(TARGET_NAME)-Bridging-Header.h と指定する.

【Swift で Objective-C クラスの呼び出し方】
Objective-Cのインスタンスメソッドの場合、 “textWithString:” のようなメソッドは “textWithString(str)” と呼び出す.
“textWithString: string:” というメソッドの場合, “textWithString(str1, string:str2)” と呼び出す.

Swift にインポートした Objective-C クラスは、そのまま継承して新しい Swift クラスを定義できる.

Swift で定義したクラスを Objective-C で使用する

【Swift クラスが定義されたヘッダーファイルをインポートする】
Swift で定義したクラスを利用するには、それを使用したい実装ファイル内に、次のように #import 文を記載する.

#import "$(PROJECT_NAME)-Swift.h

このファイルは、プロジェクト自体には登録されていないが、Derived Data の中間ファイルとして “DerivedSources” フォルダーに自動的に生成されている.