20200714のiOSに関する記事は5件です。

[2020July] iPad の UDID を Linux で確認する

はじめに

iPhone や iPad のアプリをテスト配信する際、純正の TestFlight を利用しない場合は、AdHoc 配信の延長となり、Provisioning Profile に端末の UDID を登録してビルドする、という手順から逃れられないようです。

不幸にも端末に一意の UDID が資産管理台帳に掲載されていない場合でも、トランキーロ、慌てる必要は無さそうです。

UDID は Linux でも確認できます。

手順

簡単です。lsusb -v コマンドで表示されます。

$ lsusb -v
(省略)
Bus 001 Device 010: ID 05ac:12ab Apple, Inc. iPad 4/Mini1
Device Descriptor:
  bLength                18
  bDescriptorType         1
  bcdUSB               2.00
  bDeviceClass            0 
  bDeviceSubClass         0 
  bDeviceProtocol         0 
  bMaxPacketSize0        64
  idVendor           0x05ac Apple, Inc.
  idProduct          0x12ab iPad 4/Mini1
  bcdDevice            5.03
  iManufacturer           1 Apple Inc.
  iProduct                2 iPad
  iSerial                 3 0123456789012345678901234567890123456789
  bNumConfigurations      4
(省略)

iSerial の40桁の16進数が Xcode で表示される UDID と同じ値です。

おわりに

「Windows UDID」と検索すると出てくる記事でも、同じ情報を引用する方法が載っています。なので、たぶん、しばらくこの方法が通用するんじゃないでしょうか。

まぁでも、一度だけ正規の手順で調べて台帳に書いておく、それが正解だと思います。

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

SwiftUI でアプリ全体の TintColor を変更する

SceneDelegate で色を指定する

SwiftUI プロジェクトを新規作成すると、 SceneDelegate 内に UIWindow を生成するコードがデフォルトで用意されるはずです。
生成された window の tintColor プロパティに任意の色を指定すると、アプリ全体の TintColor を変更することができます。

SceneDelegate.swift
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
    let contentView = ContentView()

    if let windowScene = scene as? UIWindowScene {
        let window = UIWindow(windowScene: windowScene)
        window.rootViewController = UIHostingController(rootView: contentView)

        // この 1 行を追加する
        window.tintColor = .systemGreen

        self.window = window
        window.makeKeyAndVisible()
    }
}

今回の例では systemGreen を指定していますが、 型は UIColor なのでアプリのイメージカラーに応じて自由に設定できます。
例のように systemColor にしておくと、ダークモード時にシステム側が上手く色を調整してくれるので大変便利です。
カスタムカラーを用いるかどうかはデザイナーさんと相談して決めてください。

window.tintColor = UIColor(displayP3Red: 1.0, green: 0.0, blue: 0.0, alpha: 1.0)

ちなみに残念ながら window.tintColor で指定した値は Preview には反映されません。

余談

SwiftUI で色を変更する手段として、 foregroundColor() , accentColor() , background() などを設定する方法があります。
これらを ContentView のようなルートとなる View に設定して色を変えるという方法も考えられます。

struct ContentView: View {
    var body: some View {
        TabView {
            ...
        }.accentColor(.green)
    }
}

この場合は Preview にも反映されるため、その恩恵を受けることができます。しかし、

色の指定が反映されない View もあるので注意が必要です。

例えば Button 内の Text がデフォルトカラーのまま表示されることを確認しています。
この挙動はおそらく不具合ではなく SwiftUI の仕様です。

全体の色を一括で変更したい場合は tintColor を指定し、accentColor() などは各 View に対して細かな指定をしたい場合のみ設定する方が良いと思います。

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

【Swift】NSLayoutAnchorを使用し、AutoLayoutをコードで実装してみる(画面の右下に固定したボタンを表示)

はじめに

いつもはStoryboardからAutoLayoutを使用し制約を付けていたのですが、
コードから制約をつける機会があり、NSLayoutAnchorを使用したので、その方法を記載したいと思います。
今回はTwitterのように、画面の右下に固定されたボタンを表示させる方法を書いていきます(UITableViewの上に設置しています)。
概要

環境

  • Swift:5.1.3
  • Xcode:11.3.1

NSLayoutAnchorの使い方

こちらの記事が詳しいです。
どの位置を起点にして制約をつけるかは、先ほどの記事を引用させていただくと下記のようになります。

プロパティ名 位置
leadingAnchor 左端
trailingAnchor 右端
topAnchor 上端
bottomAnchor 下端
centerYAnchor オブジェクトのY座標軸の中心
centerXAnchor オブジェクトのX座標軸の中心
widthAnchor オブジェクトの横幅
heightAnchor オブジェクトの縦幅

実装してみる

// UIButtonを生成する
let plusButton = UIButton()
// 必ずfalseにする(理由は後述)
plusButton.translatesAutoresizingMaskIntoConstraints = false
// tintColorを黒にする
plusButton.tintColor = .black
// グレーっぽくする
plusButton.backgroundColor = UIColor(red: 0.92, green: 0.92, blue: 0.92, alpha: 1)
// 正円にする
plusButton.layer.cornerRadius = 25
// plustButtonのImageをplus(+)に設定する
plusButton.setImage(UIImage(systemName: "plus"), for: .normal)
// ViewにplusButtonを設置する(必ず制約を設定する前に記述する)
self.view.addSubview(plusButton)

// 以下のコードから制約を設定している
// plustButtonの下端をViewの下端から-50pt(=上に50pt)
plusButton.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -50).isActive = true
// plustButtonの右端をViewの右端から-30pt(=左に30pt)
plusButton.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -30).isActive = true
// plustButtonの幅を50にする
plusButton.heightAnchor.constraint(equalToConstant: 50).isActive = true
// plusButtonの高さを50にする
plusButton.widthAnchor.constraint(equalToConstant: 50).isActive = true

注意点

制約は必ず部品を配置してから

部品を配置していないのに制約をつけることはできません。
つまり、今回で言うと制約を設定する前にself.view.addSubview(plusButton)を記述しておく必要があります。
そうしないとビルドは通りますが、下記実行時エラーでクラッシュします。

Terminating app due to uncaught exception 'NSGenericException', reason: 'Unable to activate constraint with anchors <NSLayoutYAxisAnchor:0x600003dec2c0 "UIButton:0x7fa4a7704d90.bottom"> and <NSLayoutYAxisAnchor:0x600003d6b140 "UIView:0x7fa4a77024c0.bottom"> because they have no common ancestor.  Does the constraint or its anchors reference items in different view hierarchies?  That's illegal.'

translatesAutoresizingMaskIntoConstraints は false に

translatesAutoresizingMaskIntoConstraintsはAutoLayout以前に使われていたAutosizingというレイアウトの仕組みを、AutoLayoutに変換するかどうかを設定するフラグらしいです。
デフォルトでは、trueになっていますが、Autosizingの制約とAutoLayoutの制約がコンフリクトを起こす可能性があり、意図した動作にならないことがあるのでfalseにしましょう。
今回の場合、falseに設定しないとplusButtonが表示されませんでした。

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

SwiftUI で @State の初期状態を外から受け取る

@State private var の場合

例えば、テキストを編集するための View について考えてみましょう。

  1. 前画面から既存の文字列を受け取り、 TextField の初期値として表示する
  2. 表示された文字列を修正することができる

このような仕様の場合、 1 は以下のようなコードで実現できます。

struct TextEditView: View {
    @State private var text: String

    init(text currentText: String) {
        _text = State(initialValue: currentText)
    }

    var body: some View {
        TextField("", text: $text)
    }
    ...
}

let currentText = "Today was a good day."
TextEditView(text: currentText)

あれ?こっちは??

struct TextEditView: View {
    @State private var text: String

    init(text currentText: String) {
        text = currentText    // ❗️ Return from initializer without initializing all stored properties
    }
    ...
}

初期化が完了していないというエラーが出ます。text に代入してるのに。。

実は初期化する必要があるのは、

文字列 ではなく 文字列の状態
String ではなく State<String>
text ではなく _text 1

です。 text の 初期状態 をセットしてあげる必要があります。
(初期値ではなく、初期状態と表現する方が適切かと思います。)

今回の例では currentText: StringState でラップし 状態 として代入しています。

_text = State(initialValue: currentText)

@State var の場合

こちらは素直にコンパイルが通ります。

struct TextEditView: View {
    @State var text: String
    ...
}

let currentText = "Today was a good day."
TextEditView(text: currentText)

イニシャライザが内部でうまく処理してくれてるのかな。


  1. _ (アンダースコア) を付けることで Property Wrapper 自身にアクセスできる。 

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

GitHub Actions で Flutter プロジェクトを Firebase にデプロイしたい

はじめに

数ヶ月前に構築した Flutter 用の CI / CD 環境が使われることなく眠っているので供養のためにも記事にしておきます。
困っている方の参考になれば嬉しいです。

GitHub Actions についてはこちら
https://github.co.jp/features/actions

ワークフローの概説

前提条件

プロジェクトのシークレットに下記 8 項目をセットする必要があります。

- CERTIFICATES_P12             // p12 (Base64)
- CERTIFICATES_P12_PASSWORD    // p12 のパスワード
- FIREBASE_APP_ID_ANDROID      // Android の Firebase AppID
- FIREBASE_APP_ID_IOS          // iOS の Firebase AppID
- FIREBASE_TOKEN               // Firebase のトークン
- PROVISIONING_PROFILE         // mobileprovision (Base64)
- SLACK_WEBHOOK                // Slack のトークン
- TEAM_ID                      // Apple Developer Program の TEAM ID

https://github.com/muhiro12/flutter-deploy-workflow/blob/master/README.md

テスト & モジュール作成

  build:
    # iOS の ipa を生成したいので macOS で実行する
    runs-on: macos-latest
    steps:

    - uses: actions/checkout@v2

    # ProvisioningProfile をシークレットから取り出して、プロジェクトに無理やりセットする
    - name: Import Provisioning Profile
      run: |
        mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
        echo '${{ secrets.PROVISIONING_PROFILE }}' | openssl base64 -d -out ~/Library/MobileDevice/Provisioning\ Profiles/decoded.mobileprovision

    # 証明書を設定
    - name: Import Code-Signing Certificates
      uses: Apple-Actions/import-codesign-certs@v1
      with:
        p12-file-base64: ${{ secrets.CERTIFICATES_P12 }}
        p12-password: ${{ secrets.CERTIFICATES_P12_PASSWORD }}

    # Flutter コマンド!
    - name: Flutter action
      uses: subosito/flutter-action@v1.3.0
    - run: flutter pub get
    - run: flutter test
    - run: flutter build ios
    - run: flutter build apk    # この時点で Android モジュールは作成完了

    # Xcode で Archive
    - name: iOS Build Action
      uses: yukiarrr/ios-build-action@v0.5.0
      with:
        project-path: ios/Runner.xcodeproj
        p12-base64: ${{ secrets.CERTIFICATES_P12 }}
        mobileprovision-base64: ${{ secrets.PROVISIONING_PROFILE }}
        code-signing-identity: iOS Distribution
        team-id: ${{ secrets.TEAM_ID }}
        workspace-path: ios/Runner.xcworkspace
        export-method: development
        certificate-password: ${{ secrets.CERTIFICATES_P12_PASSWORD }}
        output-path: app-release.ipa

    # デプロイは Ubuntu で行うため、一度モジュールをアップロードする
    - name: Upload artifact
      uses: actions/upload-artifact@v1.0.0
      with:
        name: ios
        path: app-release.ipa

    - name: Upload artifact
      uses: actions/upload-artifact@v1.0.0
      with:
        name: android
        path: build/app/outputs/apk/release/app-release.apk

Firebase にデプロイする

  deploy:
    needs: [build]
    # 2020/04 時点
    # Firebase-Distribution-Github-Action が macOS 未対応のため Ubuntu で実行する
    runs-on: ubuntu-latest
    steps:

    - uses: actions/checkout@v2

    # モジュールをダウンロードする
    - name: Download artifact
      uses: actions/download-artifact@v1.0.0
      with:
        name: ios

    - name: Download artifact
      uses: actions/download-artifact@v1.0.0
      with:
        name: android

    # Firebase にデプロイ!
    - name: Firebase App Distribution
      uses: wzieba/Firebase-Distribution-Github-Action@v1.2.1
      with:
        appId: ${{secrets.FIREBASE_APP_ID_IOS}}
        token: ${{secrets.FIREBASE_TOKEN}}
        groups: testers
        file: ios/app-release.ipa

    - name: Firebase App Distribution
      uses: wzieba/Firebase-Distribution-Github-Action@v1.2.1
      with:
        appId: ${{secrets.FIREBASE_APP_ID_ANDROID}}
        token: ${{secrets.FIREBASE_TOKEN}}
        groups: testers
        file: android/app-release.apk

Slack に通知する

  notify:
    needs: [build, deploy]
    runs-on: ubuntu-latest
    steps:

    - name: Slack Notification
      uses: rtCamp/action-slack-notify@v2.0.0
      env:
        SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
        SLACK_COLOR: '#171515'
        SLACK_ICON: https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png
        SLACK_USERNAME: ${{ github.repository }}
        SLACK_TITLE: Now Available
        SLACK_MESSAGE: https://appdistribution.firebase.dev/app_distro/projects

おわりに

ポイントは、 macOS で生成したモジュールを upload / download で Ubuntu に渡すところです。
全体的に結構苦労した覚えがありますが、自分のスキル的にも、環境的にも、今はもっと最適化できる気がしています。

ちなみに今は Bitrise を利用していますが、圧倒的に簡単に環境構築できます。
無料期間、無料枠などあるのでそちらも是非お試しください。

yaml ファイルの全容はこちら
https://github.com/muhiro12/flutter-deploy-workflow/blob/master/.github/workflows/main.yml

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