20190927のiOSに関する記事は13件です。

Xcode11で古いプロジェクトテンプレートを利用する

動機

先日iOS13がリリースされましたが、アプリを新規に始める場合に、iOS11〜1OS13あたりをターゲットに設定することは、まだまだあるかと思います。

開発では、新しいXcode11をメインに使用したいのですが、Single View Appテンプレートでプロジェクトを新規作成した場合、そのままターゲット設定をiOS13未満にするとScenes関連のclassが使用できないためコンパイルエラーがでます。
Scenesとは、iOS13で登場したひとつのアプリプロセスに対してマルチUI(マルチウインドウ)をサポートするための仕組みのようで、現状では主にiPadでの用途があるようです。これによってSceneDelegate.swiftと関連設定が、デフォルトのプロジェクトに含まれているためです。

ターゲットiOSバージョンによってはXcode11で、Scenes関連の設定が含まれないXcode10バージョンのSingle App Viewテンプレートもまだまだ使いたい時があるなあ、と思ったのでXcode10からコピーしてXcode11からも使えるようにしておけば便利かもと思った次第です。

というわけで、当記事ではXcodeのカスタムテンプレート追加手順を、表題の件に絞ってまとめておきます。

そもそもそういう頻度が少ない場合

そんなにプロジェクト新規作成の頻度が少ない場合、後述するテンプレートのコピーをしなくても、Xcode10&11併用環境であればiOS12〜以下をターゲットする場合、普通にXcode10.3でプロジェクトを新規作成して開始すれば大丈夫です。そうでない場合は、新規作成のためだけに古いXcodeを保持しておかなければならない、というデメリットがあります。(マイグレーションが必要な時などに役には立つのですが)

または手作業でXcode11で作成したプロジェクトのSceneDelegate.swiftなどを消すことも可能。しかし何度も行う場合には省略したいとも思います。
- Xcode 11 - Opt out of UISceneDelegate/SwiftUI on iOS 13
- How to remove SceneDelegate code from iOS 13 project?

テンプレートのコピー手順

では、Xcode10から古いタイプのSingle View Appのプロジェクトテンプレートをコピーしてきて独自のテンプレートとして、Xcode11で使えるようにします。

作業後の完成イメージのキャプチャですが、作業後にはプロジェクト作成ウィザードに画像のような形で選択出来るようになります。

スクリーンショット 2019-09-27 17.23.21.png

カスタムテンプレート用のフォルダを作成

Xcode独自テンプレートは以下のパス配下に追加する決まりになってます。

# 独自Projectテンプレートのパス
~/Library/Developer/Xcode/Templates/Project\ Template
# 独自Fileテンプレートのパス
~/Library/Developer/Xcode/Templates/File\ Template

テンプレートを置くフォルダを作成。テンプレートのカテゴリ表示はフォルダ名が反映されるのでMy Templatesという名前のフォルダを作成することにします。

$ mkdir -p ~/Library/Developer/Xcode/Templates/Project\ Templates/My\ Templates

Xcode10.3からのテンプレート複製

フォルダの準備ができましたので、次は中身です。プロジェクト新規作成ウィザードに表示されるテンプレートはXcode.appパッケージを開いた中に入ってますので、そいつをコピーします。Xcode10.3がない場合はダウンロードしてくる必要があります。Xcode.appを右クリックからパッケージの内容を表示で辿っていくか、以下のコマンドを実行して開きます。

# パスの中の`Xcode_10.3.app`部分は適宜変更してください。
open /Applications/Xcode_10.3.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/Xcode/Templates/Project\ Templates/iOS/Application/

Finderで操作するか、以下のコマンドを実行しXcode_10.3.appのパッケージの中からSingle View App.xctemplateをカスタムテンプレート用フォルダへとコピーします。

cp -r /Applications/Xcode_10.3.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/Xcode/Templates/Project\ Templates/iOS/Application/Single\ View\ App.xctemplate ~/Library/Developer/Xcode/Templates/Project\ Templates/My\ Templates/Single\ View\ App.xctemplate

追記(2019/09/29):
[重要] テンプレートのIdentifierがXcode11のオリジナルと重複していると、ウィザードにオリジナルの方のSingle View Appアイコンが表示されなくなってしまっていたので、
コピーした方のSingle View App.xctemplateの中のTemplateInfo.plistファイルを開いて(Xcodeが起動するはず)Identifier部分を何か好きな文字列に編集を行います。(↓画像の例ではオリジナルの文字列に_Xcode10.3をを付加しました。)

スクリーンショット 2019-09-29 17.06.52.png

作業は以上で完了です。

確認

Xcode11を開いて、プロジェクト新規作成のウィザードを起動します。

  • File -> New -> Project...を選択か、または、ショートカットキーでCmd + Shift + Nです。

Xcode10からカスタムテンプレートとしてコピーしたSingle View AppMy Templates項目に表示されており、選択するとScenes関連なしのプロジェクトを開始出来るようになっているはずです。

まとめ

Xcodeでは、最新のAPIに合わせたテンプレートへ変わることは珍しくないように思いますので、過渡期期間では今後も役に立つかもしれません。

参考?

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

BrightFuturesのレスポンス結果を表示するのはonSuccessのスコープ内でやろう

BrightFuturesを使用した非同期処理の場合、
APIから取得した結果を表示するためにUILable等に設定するのは、onSuccessのスコープ内でやること。
そうしないと非同期処理のため、値がまだ帰ってきていない内に描画処理が走り、想定通りに反映されない。

普段JavaScriptで書いてるときは自然としてそうだが失念してた。
Async/Awaitほしい、、

class SomeViewController: AutoLayoutViewController {

  private var someObj:SomeObject = SomeObject()
  private var someLabel: UILabel

   override func viewDidLoad() {

   [BrightFuturesを使った非同期処理]
     .onSuccess { success in
       someObj = success

       // ここが正解
       self.someLabel.text = success.text
     }

   // ここでやると反映されない
   // self.someLabel.text = success.text

  }

}

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

iOS13対応をまとめてみた

前書き

対応が必要そうなものを探してまとめてみた。
正直身に降りかからないと細かい仕様までは分からないので、現状をまとめておいて判明次第追記していく。

今すぐ対応が必要

UISerchBarのvalue(forKey:)メソッドが廃止された

必須か: 必須
内容:UIserchBarでvalue(forKey:)を使用しているとアプリクラッシュします
参考:https://qiita.com/ktanaka117/items/1a30c4c6052cd8e1a0b7

Bluetooth利用にパーミッションが必須になった

必須か:必須
いつまでに対応すべきか:今すぐ
内容:位置情報等のパーミッション同様、設定しないまま機能を使おうとすると落ちる
参考:http://harumi.sakura.ne.jp/wordpress/2019/08/27/ios13%E3%81%8B%E3%82%89%E3%81%AEbluetooth%E3%83%91%E3%83%BC%E3%83%9F%E3%83%83%E3%82%B7%E3%83%A7%E3%83%B3/

UISearchDisplayControllerがdeprecatedから完全廃止になった

必須か:必須
内容:UISearchDisplayControllerを使用しているとアプリクラッシュする
参考:https://github.com/mapbox/mapbox-gl-native/issues/15559

Sign in with Apple

必須か:アプリによっては必須
いつまでに対応すべきか:新しいアプリは必須。既存アプリは2020年4月。
内容:サードパーティ製またはソーシャル(Facebook、Google、Twitter、LinkedIn、Amazon、WeChat)のログインサービスを使用している場合は、AppleIDでのサインイン対応必須。自社システムのみのログインなら対応不要。
参考:https://developer.apple.com/app-store/review/guidelines/#sign-in-with-apple
https://qiita.com/_asa08_/items/4f6e383fc6c9d38046ed

VoIPプッシュの仕様が変わった

必須か:必須
いつまでに対応すべきか:新しいアプリは必須。既存アプリは2020年4月。
内容:VoIPプッシュの利用が着信のみになるらしい
参考:https://developer.apple.com/documentation/pushkit/responding_to_voip_notifications_from_pushkit?language=objc
https://qiita.com/sunskysoft/items/34665541d9bec438ee36

2020年4月以降は全てのアプリでiOS13対応、Xcode11でのビルドが必須

必須か:必須
いつまでに対応すべきか:新しいアプリは必須。既存アプリは2020年4月。
内容:題名の通り
参考:https://developer.apple.com/news/?id=09102019a&1568158483

今後必須になりそうなもの

ダークモード対応

必須か:準必須(いまのところ必須ではないが以後必須になる可能性が高い)
いつまでに対応すべきか:不明
参考:https://developer.apple.com/documentation/xcode/supporting_dark_mode_in_your_interface/choosing_a_specific_interface_style_for_your_ios_app?language=objc
https://qiita.com/p_on_ro/items/91e6659fda662fb2aac0

UIWebViewがiOS13以降deprecatedになった

必須か:準必須
いつまでに対応すべきか:不明
内容:ドキュメントがdeprecatedになっている。代替UIはWKWebView
参考:https://developer.apple.com/documentation/uikit/views_and_controls?language=objc

その他変更

遷移アニメーションの対応

必須か:オプション
いつまでに対応すべきか:必要に応じて
内容:遷移モードのデフォルト値が変更された。意図していない「戻る操作」が発生する可能性がある。
参考:https://qiita.com/yh_genephia/items/b82d4df36b4ef1fdcdd8

システムフォントが変更された

必須か:オプション
いつまでに対応すべきか:必要に応じて
内容:boldがiOS12以前より太くなった。
参考:https://qiita.com/a_t/items/484f7ad52e8c9fd18903

Realmが落ちることがある

必須か:オプション
いつまでに対応すべきか:必要に応じて
内容:iOS13のみエラーが発生することがあるとのこと。
参考:https://stackoverflow.com/questions/48810421/rlmexception-primary-key-property-serial-does-not-exist-on-object-book-mig

スクロールビューのインジケータの仕様が変わった

必須か:オプション
いつまでに対応すべきか:必要に応じて
内容:Could not cast value of type ‘_UIScrollViewScrollIndicator’
参考:https://qiita.com/MilanistaDev/items/8151dd2544b2eda9353b

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

OneSignalをXcode11でビルドするなら最新じゃないと動かない

はじめに

Xcode11にしたら、OneSignalでプッシュ通知が届かなくなった!
OneSignalのダッシュボードにも端末が表示されなくなった!

環境

mac
Xcode11
Swift4.2

やったこと

OneSignal最新版2.11.1を入れてビルド
Swift4.2でもOneSignal最新版2.11.1はビルド通った!

9月頭にOneSignalチームからその旨のメールが来ていたのに無視していたのが悪かった。

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

Reactorのtransform内でPublishSubjectをマップした挙動をテストした際にハマったこと

transform(mutation:)内の処理

以下のようにeventをマップしたReactor内のtransformの挙動をテストする際につまづいたポイントを書き残しておきます。

import ReactorKit
import RxSwift

final class HogeReactor: Reactor {
    ...
    func transform(mutation: Observable<Mutation>) -> Observable<Mutation> {
        return .merge(
            mutation,
            provider.accountService.event.flatMap { event -> Observable<Mutation> in
                switch event {
                case let .updateProfile(user):
                    return .just(.setUser(user))
                }
            }
        )
    }
    ...
}

eventの実装

ちなみにprovider.accountService.eventは以下のようにPublishSubjectで実装されています。

enum AccountEvent {
    case updateProfile(user: User)
}

protocol AccountServiceType {
    var event: PublishSubject<AccountEvent> { get }
    ...
}

final class AccountService: AccountServiceType {
    let event = PublishSubject<AccountEvent>()
    ...
}

テスト失敗

これをテストする際に単純にstreamを流しただけだとテストがうまくいきませんでした。

@testable import Hoge
import Nimble
import Quick

class HogeReactorSpec: QuickSpec {
    ...
    describe("state.user") {
        context("when receives updateProfile event", closure: {
            it("is updated user", closure: {
                let updatedUser = TestData.testUser()
                stubServiceProvider.accountService.event.onNext(.updateProfile(user: updatedUser))

                // Failed:stateにupdatedUserがセットされず
                expect(hogeReactor.currentState.user) == updatedUser  
            })
        })
    }
}

一度もtransform(mutation:)が呼ばれてないため、eventのマップがそもそもできていないのが原因ぽいです。

テスト通過

transform(mutation:)を呼ぶために適当にactionを呼んでやるとうまくいきました。

@testable import Hoge
import Nimble
import Quick

class HogeReactorSpec: QuickSpec {
    ...
    describe("state.user") {
        context("when receives updateProfile event", closure: {
            it("is updated user", closure: {
                // NOTE: Call any action to set up transformed mutation
                hogeReactor.action.onNext(.load)

                let updatedUser = TestData.testUser()
                stubServiceProvider.accountService.event.onNext(.updateProfile(user: updatedUser))

                // Pass:stateにupdatedUserがセットされる
                expect(hogeReactor.currentState.user) == updatedUser
            })
        })
    }
}

本当にこれが正しいのかわかりませんが、とりあえず動いたので良しとしてます。
知見ある方いましたらコメントで教えていただけると嬉しいです。

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

Xcode11で作成したプロジェクトからSceneに関する機能をオミットする

Xcode11とUISceneとUIWindowにまつわるトラブルシュートに追われている。
個別のケースに分解してお届け。

背景

Xcode10で作成したプロジェクトをXcode11で開いた場合と、Xcode11で作成したプロジェクトで UIWindowの振る舞いが異なった。この違いはプロジェクトがScene Based Lifecycleに対応しているかどうかに左右されることが分かった。

対応

簡単に言うと以下を実施すればよい。

  • AppDelegatewindowを生やす
  • Info.plistからSceneに関する項目を取り除く
  • AppDelegateからSceneに関する項目を取り除く

これにより、iOS13で動かしてもAppDelegateを基本としたアプリのように動作する。(View Hierarchyを見るとUISceneは健在なので、互換性を保っているだけっぽい)

AppDelegatewindowを生やす

Sceneに関連する物事をオミットしたあと、AppDelegateにwindowプロパティが無いとブラックアウトしてしまうので、生やす。

// AppDelegate.swift
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?
}
// AppDelegate.h
@interface AppDelegate: UIResponder <UIApplicationDelegate>

@property (nonatomic, strong) UIWindow *window;

@end

Info.plistからSceneに関する項目を取り除く

Info.plist から [Application Scene Manifest] の項目を取り除く。

ここまでの対応だけだと、iOS13でアプリを実行した場合に表示すべきStoryboardの情報が見つけられずブラックアウトする。

AppDelegateからSceneに関する項目を取り除く

AppDelegateから、Scene Based Lifecycleに関するメソッドを取り除くことで、オミットが達成される。

具体的には AppDelegate application:configurationForConnectionSceneSession:を取り除けば良い。

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

iOS実機でborder-radiusが効かない場合の対応

組み方によってはiOS実機でborder-radiusが効かない場合があるようなので対応メモ。

border-radiusが効かないケース

.hoge {
  border-radius: 5%;
}

.hoge {
  border-radius: 5px;
}

対応策

  • 指定単位に%は使わない。
  • z-indexを付与する。
.hoge {
  z-index: 1;
  border-radius: 5px;
}

より適切な方法があれば知りたい。

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

ios開発、addTargetメソッドを調べてみた

addTargetの引数

target アクションメソッドが呼ばれるオブジェクトを指定。
action 呼ばれるメソッドを記述
controlEvents UIControlEventsの定数を記述

controlEventsに入るUIControlEventsには以下がある

touchDown コントロール内でのタッチダウン
touchDownRepeat 複数回のタッチダウンイベント
touchDragInside コントロール内で指がドラッグされた
touchDragOutside コントロール外で指がドラッグされた
touchDragEnter コントロール内へ指がドラッグされた
touchDragExit コントロール内から外へドラッグされた
touchUpInside コントロール内でタッチアップされた
touchUpOutside コントロール外でタッチアップされた
touchCancel 不明
valueChanged 値の変化
primaryActionTriggered タッチアップと同じ?
editingDidBegin TextFieldでの編集開始
editingChanged TextField内の文字変化や入力
editingDidEnd 画面外タップでのTextFieldの編集終了
editingDidEndOnExit リターンキーによるTextFieldの編集終了
allTouchEvents すべてのタッチイベント
allEditingEvents TextFieldに関するイベント
applicationReserved 予備だと思う
systemReserved 予備だと思う
allEvents 全てのイベント

参考

https://pg-happy.jp/swift-addtarget.html#toc_id_1

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

CircleCI + fastlane で Firebase App Distribution に ipa をアップロードする

Firebase App Distribution とは

公開前のAndroid/iOSアプリを配布できるサービスです。
https://firebase.google.com/products/app-distribution?hl=ja

config.yml

CircleCI 上で firebase CLI を使用するため、firebase-tools をインストールしましょう。

  fad:
    steps:
      ...
      (いろいろ設定)
      ...
      - run:
          name: Install Firebase Tools
          command: npm install firebase-tools
      - run:
          name: Deploy IPA file to Firebase App Distribution
          command: |
            bundle exec fastlane ios fad

Fastfile

Fastlane に firebase_app_distribution という action が用意されているのでそれを使います。

Distribute iOS apps to testers using fastlane  |  Firebase

  desc "Push a new develop build to Firebase App Distribution"
  lane :fad do
    ...
    (ここでipaを作る)
    ...
    firebase_app_distribution(
      app: "1:123456789:ios:abcd1234",
      testers: "tester1@company.com, tester2@company.com",
      release_notes: "Lots of amazing new features to test out!",
      firebase_cli_path: "./node_modules/.bin/firebase"
    )
  end

これで継続的に Firebase App Distribution に ipa をアップロードできるようになりました!

リンク

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

Xcode11で作成したプロジェクトのDeployment Targetを下げるとブラックアウトする問題の対処法

Xcode11とUISceneとUIWindowにまつわるトラブルシュートに追われている。
個別のケースに分解してお届け。

現象

Xcode11で作成したプロジェクトをiOS13未満の環境で実行するとブラックアウトしてしまう。

再現手順

  1. Xcode11にて、新規プロジェクトの作成を行う。
    • test off
    • SwiftUI off
  2. Project設定を開き、Deployment Targetを iOS12 にする。
    • iOS13未満ならなんでもいい
  3. iOS シミュレーターのうち、iOS12のもので実行する。
    • iOS13未満ならなんでもいい

原因

コンソールに「AppDelegateUIWindow *window が必要だ」と説明されている。

2019-09-27 07:34:57.175062+0900 sample-xcode11[52812:454025] [Application] The app delegate must implement the window property if it wants to use a main storyboard file.

iOS13を対象としたアプリケーションでは、root windowは SceneDelegateが所有しているので、AppDelegateには必要ないというAppleのおせっかいである。

対応

  • AppDelegatewindowを生やす。
// AppDelegate.swift
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?
}
// AppDelegate.h
@interface AppDelegate: UIResponder <UIApplicationDelegate>

@property (nonatomic, strong) UIWindow *window;

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

【iOS】Sign In with Apple 実装してみた。

Sign in with Appleの必須化

2019/6/3に更新されたレビューガイドラインに以下の記述がありました。

Sign in with Apple will be available for beta testing this summer. It will be required as an option for users in apps that support third-party sign-in when it is commercially available later this year

※ 公式:https://developer.apple.com/news/?id=06032019j

どうやらログイン連携にサードパーティを用いているアプリには、Sign in with Apple の実装が必須になるよう…。
これは早めに試してみようということで早速実装してみました!!

環境

  • Xcode11 以上
  • 対応端末iOS13 以上

今回は自社の HiNative を使ってお試し実装してみました。
(ちゃっかり宣伝: WEBApple Store

実装

ステップはたったの3つ!結論からいうと超簡単でした。

  1. Xcodeの設定
  2. Providerを生成してリクエスト
  3. Delegateでその後の処理

1. Xcodeの設定

TARGET > Signing & CapabilitiesSign in with Apple を追加します。
スクリーンショット 2019-09-27 0.41.57.png スクリーンショット 2019-09-27 0.42.15.png

これでXcodeの設定は完了です。

1.2. (ステップには記載なし) 適当にボタン作ってね

ボタンとActionを用意してください〜。
ボタンのレイアウトは公式から指定があるので、こちらの公式を参考に!

2. Providerを生成してリクエスト

ViewController.swift
import AuthenticationServices

@objc
func authorizationAppleID() {
  if #available(iOS 13.0, *) {
    let appleIDProvider = ASAuthorizationAppleIDProvider()
    let request = appleIDProvider.createRequest()
    request.requestedScopes = [.fullName, .email]

    let authorizationController = ASAuthorizationController(authorizationRequests: [request])
    authorizationController.delegate = self
    authorizationController.performRequests()
  }
}

if #available(iOS 13.0, *) なので、ボタンの表示非表示も気をつけないといけないですね。

3. Delegateでその後の処理

ViewController.swift
extension ViewController: ASAuthorizationControllerDelegate {
    @available(iOS 13.0, *)
    func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
        if let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential {
            // 取得できる値
            let userIdentifier = appleIDCredential.user
            let fullName = appleIDCredential.fullName
            let email = appleIDCredential.email
        }
    }

    @available(iOS 13.0, *)
    func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
        // エラー処理
    }
}
補足

ASAuthorizationAppleIDCredential は 2 で生成した ASAuthorizationAppleIDProvider のリクエストが成功した際に呼ばれます。
今回はこの実装のみですが、他にも iCloud Keychainのクレデンシャル情報 を用いた実装もあるようです。
(詳しく知りたい方は下記の参考記事を見てみてください!)

動かすとこんな画面が出てきます

スクリーンショット 2019-09-27 1.20.10.png スクリーンショット 2019-09-27 1.20.42.png スクリーンショット 2019-09-27 1.21.14.png

FaceIDでログインできるので本当に楽…!!

参考にさせて頂いた記事

【iOS】対応必須かも?Sign In with Appleまとめ(第一報)

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

SwiftUIでWeb APIから取得した結果を表示する

SwiftUIのViewを更新するためにはデータに応じて、適切な状態の宣言をする必要があります。

ここでは、Web APIと通信し結果を表示するという、よくあるアプリをSwiftUIで実装することで、その方法を紹介します。
必要な実装は以下の2つです。

  1. ObservableObject に準拠するクラスを作成し変更の通知を発行
  2. SwiftUIのViewで通知を監視

それでは、GitHubのフォローしているユーザー一覧を表示するアプリを例にし、実装していきます。

ObservableObjectに準拠するクラスを作成し変更の通知を発行

通信を行い取得したユーザーを保持するクラスを作成し、ObservableObject に準拠させます。(準拠させると言ってもクラスであれば特に何かを実装する必要は無いです)
そして、保持したユーザーの変更を通知するためには、 @Published 属性をプロパティに付与します。

import Foundation
import Combine

class FollowingUserStore: ObservableObject {
    @Published var users: [User] = []

    init() {
        load()
    }

    func load() {
        let url = URL(string: "https://api.github.com/users/maoyama/following")!
        URLSession.shared.dataTask(with: url) { data, response, error in
            DispatchQueue.main.async {
                self.users = try! JSONDecoder().decode([User].self, from: data!)
            }
        }.resume()
    }
}

struct User: Decodable, Identifiable {
    var id: Int
    var login: String
}

たったこれだけの実装で、通信を行いユーザーを取得できた際の通知を発行することが可能になります。

SwiftUIのViewで通知を監視

今回のような、あるViewだけで使用するデータの通知を監視すれば良い場合はProperty WrapperのObservedObject を使う方法が適切です。
ObservableObject をプロパティに保持し、@ObservedObject を付与します。

import SwiftUI

struct ContentView: View {
    @ObservedObject var store = FollowingUserStore()

    var body: some View {
        List(store.users) { (user) in
            UserRow(user: user)
        }
    }
}

struct UserRow: View {
    var user: User

    var body: some View {
        Text(user.login)
    }
}

これで FollowingUserStore.users の変更を監視できます。

まとめ

これだけの実装でWeb APIから取得した結果を表示することができました。

Sep-27-2019 00-01-57.gif

また、適切な宣言をするだけで、データの同期を手動ですることなく、SwiftUIとCombineが自動で行ってくれています。これにより、コードがとてもシンプルで読みやすくなることが期待できそうです。

参考資料

https://developer.apple.com/documentation/swiftui/state_and_data_flow
https://developer.apple.com/videos/play/wwdc2019/226/
https://developer.apple.com/documentation/combine/observableobject

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

【Swift】Alamofire, MoyaのURLEncodingで、Boolが0,1になってしまうことへの対応

事象: Moyaを使ってAPIをリクエストしたが、Boolが0/1で送られていた

API系のライブラリとしてよく用いられるのが、 Moya というライブラリだと思います。

私自身も99%のプロジェクトでMoyaを使っています。

Moyaでリクエストパラメータを送る際に以下の形式で送っています。

requestParameters(parameters: [String : Any], encoding: Moya.ParameterEncoding)

しかしながら、この形式で送ると、Bool型が一度Any型に変換され、そして、最終的にNSNumberとしてリクエストされるようです。

詳しくは こちら

とはいえ、↑この人が言うように、BoolをAnyにすると、NSNumberになる的な事象は、playground上では確認できませんでした。。

対応: Bool.description で、Bool型をString型に変更して対応

Bool型→Any型: NSNumber (0/1)
Bool型→String型→Any型: Bool (true/false)

↑のようになるようです。

元々のコード

var parameters: [String: Any]? {
    return ["key" : boolParam]
}

↓↓↓↓↓↓↓↓↓

修正後のコード

var parameters: [String: Any]? {
    return ["key" : boolParam.description]
}

その他

パケットキャプチャで確認しましたが、こちら↓とても参考になりました。
通信系のデバッグには Charles が便利

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