20190821のSwiftに関する記事は10件です。

Swiftlintで警告がでないようにする

Swiftlintを導入すると、いろいろと警告を出してもらえるので便利な反面、エラーが出すぎるとエディタの可視性が悪くなります。
そこで警告を消す方法です。

// swiftlint: disable type-body-length
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ドキュメントはリリースノートもしっかり見たほうがいいよって話

経緯

初めて自作iOSアプリを作っていて、公式のドキュメントにしたがってGoogleログインを実装しているときに詰まったので備忘録。

Value of type 'GIDSignIn?' has no member 'presentViewController'

ただSign inボタンを足したいだけだったのに

https://developers.google.com/identity/sign-in/ios/sign-in?ver=swift

Google先生の言いなりになって、 viewDidLoad() 内に以下のメソッドを追加しました。

LoginViewController.swift
override func viewDidLoad() {
  super.viewDidLoad()

  GIDSignIn.sharedInstance()?.presentingViewController = self

  // Automatically sign in the user.
  GIDSignIn.sharedInstance()?.restorePreviousSignIn()

  // ...
}

怒られました

Screen Shot 2019-08-21 at 20.50.18.png

ググった

Screen Shot 2019-08-21 at 20.54.05.png

検索結果: 2件

GIDSignIn

このクラスを探してみても、 presentingViewController はあるみたいですが、出てこず。
Screen Shot 2019-08-21 at 20.57.44.png

https://developers.google.com/identity/sign-in/ios/reference/Classes/GIDSignIn

ふと思った

あれ、そもそもなんで必要なんだっけ?

  GIDSignIn.sharedInstance()?.presentingViewController = self

  // Automatically sign in the user.
  GIDSignIn.sharedInstance()?.restorePreviousSignIn()

:thinking:

一旦無視することにした

使われていないんですかね。
一旦このまま進めることにします。

追記

見つけてしまった。
このメソッドは廃止されたようでした?
https://developers.google.com/identity/sign-in/ios/release

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

Value of type 'GIDSignIn?' has no member 'presentViewController'

経緯

初めて自作iOSアプリを作っていて、公式のドキュメントにしたがってGoogleログインを実装しているときに詰まったので備忘録。

ただSign inボタンを足したいだけだったのに

https://developers.google.com/identity/sign-in/ios/sign-in?ver=swift

Google先生の言いなりになって、 viewDidLoad() 内に以下のメソッドを追加しました。

LoginViewController.swift
override func viewDidLoad() {
  super.viewDidLoad()

  GIDSignIn.sharedInstance()?.presentingViewController = self

  // Automatically sign in the user.
  GIDSignIn.sharedInstance()?.restorePreviousSignIn()

  // ...
}

怒られました

Screen Shot 2019-08-21 at 20.50.18.png

ググった

Screen Shot 2019-08-21 at 20.54.05.png

検索結果: 2件

GIDSignIn

このクラスを探してみても、 presentingViewController はあるみたいですが、出てこず。
Screen Shot 2019-08-21 at 20.57.44.png

https://developers.google.com/identity/sign-in/ios/reference/Classes/GIDSignIn

ふと思った

あれ、そもそもなんで必要なんだっけ?

  GIDSignIn.sharedInstance()?.presentingViewController = self

  // Automatically sign in the user.
  GIDSignIn.sharedInstance()?.restorePreviousSignIn()

:thinking:

一旦無視することにした

使われていないんですかね。
一旦このまま進めることにします。

追記

見つけてしまった。
https://developers.google.com/identity/sign-in/ios/release

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

Xcodeを消してから入れ直したらSimulator(シミュレータ)が消えた

Xcodeを入れ直した

私はAppleのサイトから直接Xcodeをダウンロードしていた為、更新が楽な様にAppleStoreの方からダウンロードをし直しました。その後、ビルドしてSimulator(シミュレータ)を起動しようとしたらデバイスが一つも見当たらなかったのです。

15152e70c4f59d8fcfaea58f5e8edc67.jpg

Simulator(シミュレータ)が入ってない

結果としては、新しいXcodeの方にSimulator(シミュレータ)が入ってなかったのが原因でした。入れ直す方法は、Xcodeを起動し、Xcode→Preferences→Componentsとクリックしていき、ダウンロードしたいSimulator(シミュレータ)のバージョンをクリックするだけです。

10daf20f99bcb60d6d3b8f7dde4b49cf.png

ダウンロード後

c6fcc9035efdd90d49450b9fb3d2e340.jpg
f26080022ac8520e73f7d1ce138fe487.jpg
Xcode側のiosのバージョンを二箇所、ダウンロードしたSimulator(シミュレータ)のバージョンに合わせれば無事に反映されます。
87e2cdbc77617a937fe07180004e14c9.jpg

左上にiPhoneXsが反映されています。
ちなみに、アップデート後は変なエラーが出る可能性があるので、macを再起動した方が良いです

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

[Swift5]Codableソースコード解析①

Codableから説明

関連するファイルは二つあります:

  1. gybにユーザーに公開しているprotocolおよびCodabelを実現しているSwiftのタイプ
  2. JSONEncoder.swift。JSONEncoderとJSONDecoderを実現しているファイルです。
    続きましては、Codableの定義はここです:
qiita.swift
public typealias Codable = Encodable & Decodable

Encodable

Encodableのタイプはなんでしょうか?実際、encode(to:)メソッドを提供しているだけです。

qiita.swift
public protocol Encodable {
  /// Encodes this value into the given encoder.
  ///
  /// If the value fails to encode anything, `encoder` will encode an empty
  /// keyed container in its place.
  ///
  /// This function throws an error if any values are invalid for the given
  /// encoder's format.
  ///
  /// - Parameter encoder: The encoder to write data to.
  func encode(to encoder: Encoder) throws
}

ここまで、三つの質問を思い出すと:
1.SwiftがサポートしているEncodableのタイプは全部このメソッドを実現していますか?
2.SwiftではデフォルトでEncodableをサポートしているメソッドはなんでしょうか?
3.Encoderは何ですか?

質問1の答えはイエスです。Codable.swift.gybファイルにデフォルトタイプの実現があります。
例えば、Intのデフォルト実現は:

qiita.swift
extension Int : Codable {
  public func encode(to encoder: Encoder) throws {
    var container = encoder.singleValueContainer()
    try container.encode(self)
  }
}

Arrayのデフォルト実現は:

qiita.swift
extension Array : Encodable where Element : Encodable {
  public func encode(to encoder: Encoder) throws {
    var container = encoder.unkeyedContainer()
    for element in self {
      try container.encode(element)
    }
  }
}

なるほど、Swiftでは本当にEncodableのタイプはメソッドを実現しています。

そしたら、Swiftでは、Encodableをサポートしているタイプはどれくらいありますか?
Codable.swift.gybファイルに下記のコードがあります。

qiita.swift
%{
codable_types = ['Bool', 'String', 'Double', 'Float',
                 'Int', 'Int8', 'Int16', 'Int32', 'Int64',
                 'UInt', 'UInt8', 'UInt16', 'UInt32', 'UInt64']
}%

上記以外、ファイルの一番下に、Array / Set / Dictionary / Optionalなどもあります。

ここまで、質問3だけ残ってます。
encodeメソッドのEncoderはなんでしょうかね。ブラックボックスとして考えれば、結果的にSwiftのオブジェクトをJSON文字列にエンコディングされています。

JSONEncoder

JSONEncodeの定義:

  • JSONエンコーディング結果の出力フォマット(public struct OutputFormatting)
  • Dateタイプのエンコーディング方式(public enum DateEncodingStrategy)
  • Dataタイプのエンコーディング方式(public enum DataEncodingStrategy)
  • 例外的な浮動小数点エンコーディング方式(public enum NonConformingFloatEncodingStrategy)
  • JSONのkeyのエンコーディング方式(public enum KeyEncodingStrategy)

続きましては、JSONEncoderを使用してエンコーディングする時、デフォルト使用するプロパティ:

qiita.swift
open class JSONEncoder {
  open var outputFormatting: OutputFormatting = []

  open var dateEncodingStrategy: DateEncodingStrategy = .deferredToDate

  open var dataEncodingStrategy: DataEncodingStrategy = .base64

  open var nonConformingFloatEncodingStrategy:
    NonConformingFloatEncodingStrategy = .throw

  open var keyEncodingStrategy: KeyEncodingStrategy = .useDefaultKeys
}

デフォルトのプロパティを便利に使う為、内部タイプの_Optionsと内部プロパティのoptionsも定義されています。

qiita.swift
open class JSONEncoder {
  fileprivate struct _Options {
    let dateEncodingStrategy: DateEncodingStrategy
    let dataEncodingStrategy: DataEncodingStrategy
    let nonConformingFloatEncodingStrategy:
      NonConformingFloatEncodingStrategy
    let keyEncodingStrategy: KeyEncodingStrategy
    let userInfo: [CodingUserInfoKey : Any]
  }

  fileprivate var options: _Options {
    return _Options(dateEncodingStrategy: dateEncodingStrategy,
      dataEncodingStrategy: dataEncodingStrategy,
      nonConformingFloatEncodingStrategy: nonConformingFloatEncodingStrategy,
      keyEncodingStrategy: keyEncodingStrategy,
      userInfo: userInfo)
  }
}

そして、JSONEncoderのコンストラクタ(only one):

qiita.swift
open class JSONEncoder {
  public init() {}
}

最後は、我々が使用するencodeメソッド:

qiita.swift
open func encode<T : Encodable>(_ value: T) throws -> Data

__JSONEncoder

これから、JSONEncoder.encodeメソッドの実現から見てみましょう。関数定義はここです。
実行ロジック:

qiita.swift
open func encode<T : Encodable>(_ value: T) throws -> Data {
  let encoder = __JSONEncoder(options: self.options)

  guard let topLevel = try encoder.box_(value) else {
    /// throw exception
  }

  /// Handle invalid box value.

  let writingOptions =
    JSONSerialization.WritingOptions(
      rawValue: self.outputFormatting.rawValue)
  do {
     return try JSONSerialization.data(
      withJSONObject: topLevel, options: writingOptions)
  } catch {
    /// throw exception
  }
}

encodeの実現:

  • まず、Encoderタイプの__JSONEncoderオブジェクトを作る
  • そして、box_メソッドでパラメーターvalueをJSONエンコーディングできるデーターに変換する
  • エラーチェックなどされたら、FoundationのJSONSerialization.dataを呼び出し、データーのエンコーディングできて、エンコーディング結果のDataオブジェクトをreturn
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ウェブフロントエンドエンジニアがiOSアプリ開発をする際に知っておきたかったこと

はじめに

この記事はふだんウェブフロントエンド(javascript/html/css)の業務をしているエンジニアが、iOS開発の予習に取り組んだ際に躓いた点を共有し、これからiOS開発を始めたいエンジニアの助けになることを目的としたものです。

XcodeのバージョンとSwiftのバージョン

当然これらは固定して開発に望みたいので、以下の記事を参考にしました。

http://kimagureneet.hatenablog.com/entry/2019/04/08/125129

プロジェクト内のファイルツリーと実ファイルの関係性

プロジェクトの左側のファイルツリーにある黄色と、青色のフォルダアイコンに困惑しました。

http://kurogomapurin.hatenablog.com/entry/2014/06/05/012415
https://qiita.com/nmbakfm/items/8f2e303ee4ec71640008
https://qiita.com/yimajo/items/6cffb5cd5a5dd659edb4

UI実装/レイアウト

ストーリーボードを分割する

ご存知ストーリーボード、このファイルの実体(foo.storyboard)はxmlファイルであり、Xcode上の操作が即座にこのxmlファイルに反映されます。

しかしこのファイルはヒューマンリーダブルとはなかなか言いにくいものです。

開発はGitでの管理下で行っていると思いますので、ストーリーボードのファイルが大きくなると目が潰れそうな差分が多く発生します。

そのため、ストーリーボードは初期の段階から適切な大きさに分割しておくのが良いでしょう。
私が開発に参加したアプリは一般的なタブビューのレイアウトだったので、初期の段階でタブごとにストーリーボードを分割しました。

https://qiita.com/Simmon/items/2af23fab4a42ab7247ba

UINavigatorController

一般的なアプリによくある、一覧画面->詳細画面に遷移するデザインは、UINavigatorController と TableViewなどで実装します。

UINavigatorControllerを利用する場合、ストーリーボード上でコンテンツとなるviewControllerにembedする操作を行うのですが、この際のストーリーボード上での配置が直感的ではなく、最初困惑しました。

image.png

並列に配置されているこれらのviewControllerは

A navigation controller is a container view controller—that is, it embeds the content of other view controllers inside of itself.

https://developer.apple.com/documentation/uikit/uinavigationcontroller

ということで、実際には左側のNavigation Controllerはコンテナの役割を果たしているのですね。

カスタムビューの実装

ストーリーボード上のUI開発で最初に面を食らったポイントは、UIViewと言われる汎用のUIクラスはborderなど、レイアウトにかかせない要素をリアルタイムに確認する術を持たないことです。
webでUIを作る際にはcssで border: 1px solid blackなどと書いていけばカンタンに進められる作業ももう少し長くコードを書いて実装する必要があります。

extentionを書いておくとストーリーボードでも確認できるようになるので、最初のうちに勉強しておくとあとがラクになります。

https://qiita.com/taji-taji/items/17ad3389b3d250fcad6e
https://qiita.com/xxminamixx/items/ee8435a4e07d31cf28fd

ベクターイメージ

PDF!!!
svgじゃなく!!!
PDF使うのか!!!

コードとの接続

アクション/アウトレットについて
https://swift-ios.keicode.com/ios/action.php
https://swift-ios.keicode.com/ios/outlet.php

なお接続は適切に外さないと、ビルド時にエラーが起きます。
https://pg-happy.jp/xcode-delete-action-outlet-connection.html
https://qiita.com/Atsushi_/items/f7930dd00a2c2ea464cd

UIStackView

cssのブロックモデルに近い感覚でレイアウトできるのがUIstackviewです。
このClassが提供されてから、レイアウトの実装はだいぶラクになっただろうとたやすく想像できる非常に便利なClassです。
積極的に使いましょう。

https://qiita.com/taka1068/items/69273f05d34cfbeb3679

autoLayout

ストーリーボードでのレイアウトで、意外と気に入ったのがautoLayoutです。
触ってて嬉しかったのが、重複する制約に優先度をつけられること。cssのイメージでいうと、 !importantに優先度をつけられるかんじ。

1日さわっていれば慣れます。

https://qiita.com/_ha1f/items/5c292bb6a4617da60d4f

autoLayoutだけでもこういうのができる
https://blog.kishikawakatsumi.com/entry/2018/11/05/044228

ちなみにautoLayoutの制約もOutletでコードと関連づけられるので、状態に応じて制御したいときに重宝します。
https://nackpan.net/blog/2015/08/16/autolayout-constraints-can-connect-code/

segueによる画面遷移

https://qiita.com/fromage-blanc/items/b3cb0e7833a1d5659463

ロジック(Swift)

JSやTSでの開発と違って面白いなーと思った点

delegate

https://qiita.com/mochizukikotaro/items/a5bc60d92aa2d6fe52ca
https://qiita.com/narukun/items/326bd50a78cf34371169

関数のオーバーロード

https://wp-p.info/tpl_rep.php?cat=swift-biginner&fl=r23

Optional型の安全なunwrap

https://qiita.com/maiki055/items/b24378a3707bd35a31a8
https://qiita.com/mokumoku/items/5fef496f4e97a053e53e

外部ライブラリの導入

ビルド速度の低下のことを考えると、carthageで配布されているものはそちらを選んだほうが良さそうです。

cocoaPods

https://qiita.com/ShinokiRyosei/items/3090290cb72434852460

carthage

https://qiita.com/yutat93/items/97fe9bc2bf2e97da7ec1


細かな点をあげればきりがないですが、勉強を始める前に自分が知りたかったポイントとしてはこんなところでしょうか。

iOS開発ではUIKitの強力さとSwiftの表現力の高さに感銘を受けました。
ふだんやっているwebアプリ開発でもUIKitくらい強力なファウンデーションがあるとラクなんですけどねえ。

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

Fluxアーキテクチャのメモ

この記事はSwiftベースで話を進めています。
iOSを普段書いている方には記事を呼んで頂きたいですが、
それ以外が主戦場の方は別の記事でFluxについての理解を深めることを推奨します。

Fluxとは

Facebookが作成したアーキテクチャパターンでJSの文化から輸入してきた物です。
(そもそもClientの設計パターンはほとんどJSの文化で生まれた物ばかりです)

下の画像からわかることとして
Action -> Dispatcher -> Store -> Viewのフローがあって、
その後、
View -> Action -> Dispatcherのフローが存在します。
これによって一つのライフサイクルが作成されていることがわかります。
flux-pattern.png

Fluxの設計パターンの最大の特徴はこのAction -> Dispatcher -> Store -> Viewの単一方法のデータの受け渡しが行われていることにあります。逆に言えばこのパターンが逸脱したいと思った時はFluxを採用することをやめるべきとも言えます。(そのときはMVC,MVVM,Redux等を利用することを検討することになると思います)

他の設計パターンと比較する

すべてのアーキテクチャとの比較をすることはめんどくさい 時間がかかってしまうので今回はいくつかある設計パターンのうち、その中でも似ているMVVM(Model,View,ViewModel)と比較することにしましょう。
スクリーンショット 2019-08-21 14.27.57.png
このとき、最大の違いはViewModelDispatcher/Storeに書き換えてみるとわかります。
スクリーンショット 2019-08-21 14.32.33.png
図をみてわかる通り、FluxMVVMの最大の違いは

Viewが一つ前の層であるViewModelをみることで変更をみることができるのがMVVM
Viewがみることができるのは2つ前のDispatcher(StoreとActionの中継役)をみるのがFlux

です。なのでFluxでは値をもらうStoreが中で何をしているのか全く知らない状態になるという意味です。これはMVVMに比べてFluxの方が制約が厳しいという意味に直結します。

各役割の説明

なんとなく雰囲気でわかったものの、各役職がどんなことを実際に説明していなかったので説明します。

  • Action
    最終的にViewに渡したいdataを保持している場所。

  • Dispatcher
    ActionStoreの中継場所。
    Viewで入力された値とかはここが受け取ることで再度Storeに渡して
    Viewに反映したりする。

  • Store
    値の状態を管理する場所。

  • View
    UIViewControllerUIViewがこれにあたる。UIViewControllerの場合、ViewControllerとしてではなく、なるべくControllerとしての役割を剥いで、Viewとして利用する。

メリット・デメリット

メリット
- 単一方向であることを制約に掲げているのでデータフローが役割ごとに分けられることで理解しやすくなる。
- MVCの設計よりもUIViewControllerから状態管理のためのコードが減ってViewControllerとしてではなく、Viewとしての役割に近い状態になる

デメリット
- そもそもFluxを採用するほどの規模のアプリは少ない。(小中規模でこれほどの制約は返って開発コストが高くつきそう)

今回はこれで以上です。サンプルコードを追加した上での説明は今回は省きます(追加する予定はあります)

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

CombineでObservable.create/SignalProducer.initに相当するPublisherを作る

Combine には当初、クロージャを通じて複数の値の出力と成否を送る Publisher (RxSwift における Observable.create または ReactiveSwift における SignalProducer.init) が実装されていたようなのですが、途中で削除されてしまったらしい...
どうしても使いたい場面があったため、Combineの学習がてら自分で実装してみました。

こちらの投稿を参照にさせていただきました?‍♂️
https://qiita.com/shiz/items/58abf44b77d9da2042f1

SomePublisher.swift
import Foundation
import Combine

// 名前は適当です...
struct SomePublisher<Output, Failure: Swift.Error>: Publisher {
    enum Event {
        case value(Output)
        case finished
        case failure(Failure)
    }
    typealias Handler = (Event) -> Void
    private let handler: (@escaping Handler) -> Void

    private class Subscription<S: Subscriber>: Combine.Subscription where S.Input == Output, S.Failure == Failure {
        private var subscriber: S?
        private let handler: (@escaping Handler) -> Void

        init(subscriber: S, handler: @escaping (@escaping Handler) -> Void) {
            self.subscriber = subscriber
            self.handler = handler
        }

        func request(_ demand: Subscribers.Demand) {
            self.handler(self.handle)
        }

        func cancel() {
            self.subscriber = nil
        }

        private func handle(_ event: Event) {
            guard let subscriber = self.subscriber else {
                return
            }

            switch event {
            case .value(let input):
                _ = subscriber.receive(input)
            case .finished:
                subscriber.receive(completion: .finished)
            case .failure(let error):
                subscriber.receive(completion: .failure(error))
            }
        }
    }

    init(_ handler: @escaping (@escaping Handler) -> Void) {
        self.handler = handler
    }

    func receive<S>(subscriber: S) where S : Subscriber, S.Failure == Failure, S.Input == Output {
        let subscription = Subscription(subscriber: subscriber, handler: self.handler)
        subscriber.receive(subscription: subscription)
    }
}

使い方はこんな感じ。

func values(count: Int) -> SomePublisher<Int, Never> {
    return .init { (publisher) in
        // ここにストリームを実装する...
        for i in 0..<count {
            publisher(.value(i))
        }
        publisher(.finished)
    }
}

本当はイニシャライザを以下のような仕様にしたかったのですが、 Subscriber の管理と受け渡しがうまく実装できなかったので、 Event 型を使ってストリームする実装にしています。

    init<S>(_ handler: @escaping (S) -> Void) where S : Subscriber, S.Failure == Failure, S.Input == Output {
        ...
    }

Combineに実装が復活するといいですね。

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

Class 'hogeClass' has no initializers って出た時の対処法[Swift]

原因

classっていちいちinit()せなダメだっけ?と思っていたところ原因はこれだった

定義してた変数がオプショナル型でなかった
nilを許して!→初期化して! ということらしい

対処

オプショナル型にする(hogehogeDelegatehogehogeDelegate?)

class hogeClass {

    private var delegate: hogehogeDelegate?
//private var delegate: hogehogeDelegate だとエラー出る

}

参考

swift初心者:「Class 'ViewController' has no initializers」の対処方法 - Qiita

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

NSRangeからSwiftのRangeへの置換方法

はじめに

NSRangeはlocation(位置)とlength(長さ)の2つを持つ、範囲を表す構造体です。
SwiftではRangeとして範囲の扱いについて再定義されています。
NSを外せば置き換えられません。別物といってもいいくらいです。

対比表

とりあえずNSRangeからRangeに置き換えたいときは、この対比表で置き換えてください。

NSRange Range
開始位置 location startIndex
長さ length count

NSRange

文字列から取得する方法

NSString *text = @"つれづれなるままに";
NSRange rangeTest = [text rangeOfString:@"ままに"];

また、NSMakeRange で作成することもできます。

NSRange range = NSMakeRange(3, 5);

参照

NSRange - Foundation | Apple Developer Documentation
Range - Swift Standard Library | Apple Developer Documentation

NSRangeの使い方 - Object for cutie
swift - How to convert Range in NSRange? - Stack Overflow
[Swift2]Stringの Range と NSRange の変換 : 永遠日誌

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