Flutterアプリの配布方法
はじめに
Flutterで試しに作成したアプリを社内で共有したいということでやり方を調べてみました。
iOSはTestFlight、AndroidはAppDistributionを使って配布します。
iOSアプリ
アプリの設定の変更
XCode上からアプリアイコンに1024*1024アイコンを追加します。Alphaチャンネルがあるとエラーになります。
XCode上のBuild SettingsからEnable Bitcodeを有効にします。
Certificates, Identifiers & Profiles・ App Store Connect環境の設定
Certificates, Identifiers & Profilesにログインして、アプリのバンドルIDを登録します。AppStoreConnectにログインして、マイAppから新規アプリを追加します。
TestFlightの設定画面を表示してテスト情報に最低限のテスト用の設定を追加します。
フィードバックメールアドレス・連絡先情報(性・名・電話番号・メールアドレス)
ビルド&アップロード
AndroidStudioを起動して
Build > Flutter > Build iOSを選択します。Organizerから
Distribute Appボタンでアップロードします。アップロードが終わったら外部テスターグループのビルドに追加します。
アプリの審査が通ったらパブリックリンクを有効にしてリンクを関係者に渡します。
Androidアプリ
アプリの設定の変更
- keystoreファイルを作成し設定を追加します。ほぼ公式ドキュメントの内容で問題ないかと思います。 https://docs.flutter.dev/deployment/android
AppDistribution(Firebase)環境の追加
Firebaseにログインしてプロジェクトを追加します。
プロジェクト設定からアプリを追加します。
テスターとグループにグループを追加します。
ビルド&アップロード
AndroidStudioを起動して
Build > Flutter > Build APKを選択します。指定フォルダにapkファイルができているので、それをAppDistributionにアップロードします。
アップロード後にテスターを選択します。
招待リンクからテスターのリンクを取得して関係者に配布します
さいごに
Flutterアプリの配布というよりほぼ通常のアプリの配布の設定がほとんどでした。普段アプリを配布したりしている方には当たり前の内容だったかもしれません。新規に設定する機会も多くないのでメモとして残せたのはよかったかなと思います。
参考資料
AppleWatchを修理に出す話
はじめに
いま使っているAppleWatch(Series4)が購入してもう3年経つため、さすがにバッテリーがダメになってきました。 朝充電して夕方もう一度充電しないと持ちません。 ということでAppleにバッテリー交換の修理を出すことにしました。
今回は端末を自宅まで取りに来てくれる配送修理 (オンライン手続き)のサービスで申し込みました。
やったこと
修理の申し込み
まずApplewatchの設定からバッテリーの状態を確認すると「最大容量:76%」となっていました。(80%を切らないと修理してもらえないらしい)
バッテリーの画面にリンクが表示されていたので、修理の案内ページのリンクが確認できたのでそれをMacから開きました。
修理申し込みのページが表示され住所・支払い方法・引き取り日時と入力して、最後にAppleWatchとのペアリングを解除するように言われます。
ペアリングを解除すると修理の申し込みが可能となります。
AppleWatchの引き取り
翌日引き取り時間になると宅配業者が来てくれました。
引き取り時にはAppleWatch単体のみを用意すればOKでした。箱詰めなどは必要なくバンドも外して以下の状態のものを渡すだけでした。

修理の進捗
修理の進捗は専用ページで随時確認できたので安心できました。 「製品が到着しました」とか「修理が完了しました」とか「発送しました」とか進捗が出てきました。
AppleWatchの受け取り
修理が終わると、以下の感じの箱に入れられて送られてきました。

結果
引き取ってもらってから3日で修理完了しました。思っていたより簡単で早く対応してもらえました。 こんなことなら早く修理してもらえば良かった。
あと今回は修理で申し込みましたが、別のシリアル番号のAppleWatchだったので結局交換になったようです。 細かな傷もなくなってるのでまあいいかって感じです。
今後も機会があれば配送修理 (オンライン手続き)で修理出そうかなと思います
Objective-CからSwiftメソッドを呼び出す方法
はじめに
いまだにObjective-Cのコードを触る機会がありますが、そこからSwiftのコードを呼び出そうとした時にいつもどうやるんだっけ?となるのでメモがてら記事にしようと思います。
やること
Swift側
呼び出したいクラスとメソッドに@objcをつけます。あとクラスはNSObjectを継承します
@objc class FooClass: NSObject {
@objc func foo() {
:
}
:
}
@objcMembersでクラス全体を呼び出せるようになります
@objcMembers
class FooClass: NSObject {
func foo() {
:
}
:
}
Objective-C側
ProductName-Swift.hをインポートします。あとは普通に呼び出します
#import "ProductName-Swift.h"
- (void)bar
{
:
[[FooClass alloc] init] foo];
:
}
さいごに
たまにしか使わないので結構忘れます。
先日もNSObjectの付け忘れで結構悩んでしまいました。今後はこちらのメモを見るようにしようと思います
参考資料
UITableViewのSectionHeaderの位置がおかしい
はじめに
先日、作成した画面のUITableViewのsectionHeaderの位置が微妙にずれていてNavigationBarに隠れてしまっているのが発覚しました。 原因がよく分からなかったので色々と調べてみました。
現象
セクションタイトル1がNavigationBarの下に少し隠れてしまってます。

原因
これよくあるExtend EdgesかTranslucentの設定ミスですぐ直るかと思ったら全然うまくいかず、設定をいろいろ変えてみてもうまくいかず...。
最終的にはうまく動いている画面と設定を片っぱしから比較してやっと見つけました。

うまくいっている画面はここがAutomaticではなく0になっていました。
今までいじったことがなかった設定なのでなんの設定?っと思って調べてみたら、sectionHeaderの高さの見積り値でこれが0だと見積り値を使わなくなるとのこと。
さらにiOS10とiOS11でデフォルト値が違うため明示的に指定していないとiOS11以降で動作が変わります。
ということでここを明示的に0を指定して見積り値を使わないようにしたところ正しく動作するようになりました。
よかった。
さいごに
OSやXcodeのアップデートで動作が変わることがあるのはなかなか気が付かないので結構怖いなと。 今回はうまくいっている画面がたまたまあり、それと比較して問題が見つけられたのですが今後もこんなことがあるとちょっと困るなと思いました。
丸一日かかってしまいましたが無事修正できてよかったです。
参考資料
https://qiita.com/masashi-sutou/items/405adf126d0d2c4f0b4e https://armlock.hatenablog.com/entry/2018/05/27/152606
iOS13から使えるようになったCombineを使ってみる
はじめに
私が担当しているアプリも近々iOS12以降対応だったのがiOS13以降対応になる。 いままで使えなかったCombine関連もいよいよ使うことができるので調べてみた。
サンプル実装
とりあえずviewDidLoad()に追加して実行してみました。
import UIKit
import Combine
class ViewController: UIViewController {
var cancellable: AnyCancellable? = nil
override func viewDidLoad() {
super.viewDidLoad()
let publisher = CurrentValueSubject<Int, Never>(1)
cancellable = publisher.sink { a in
print(a)
}
publisher.value = 2
publisher.value = 3
}
}
結果
1 2 3
publisherに値をセットするたびに、sinkの処理が実行されています。
さいごに
今回はほんの一部だけ調査しました。 いままでViewModelの変更をViewに伝えるのにRxSwiftを使っていましたが、その処理はこちらに置き換えできそうです。 今後時間ができたらもっと深く調査しようかと思います。
参考資料
Swiftのasync/awaitを試してみる
はじめに
Swift5.5からasync/awaitが使えるようになったのでどうやって使うのか調べてみました
サンプル実装
とりあえずviewDidLoad()に追加して実行してみました。
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
Task {
print(Date().description)
await waitMethod()
print(Date().description)
await waitMethod()
print(Date().description)
}
}
// 非同期メソッド
func waitMethod() async {
Thread.sleep(forTimeInterval: 10.0)
}
}
結果
2021-11-11 01:31:22 +0000 2021-11-11 01:31:32 +0000 2021-11-11 01:31:42 +0000
10秒ごとにログが出力されました。きちんと非同期メソッドが終わってから次の処理が呼ばれているのがわかります。
サンプル実装2(同期・非同期の組み合わせ)
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
Task {
print(Date().description)
let msg = await msgMethod()
print(Date().description)
print(msg)
}
}
// 非同期メソッド
func msg1Method() async -> String {
Thread.sleep(forTimeInterval: 10.0)
return "msg1"
}
// 非同期メソッド
func msg2Method() async -> String {
Thread.sleep(forTimeInterval: 10.0)
return "msg2"
}
// 非同期メソッドの同期
func msgMethod() async -> String {
async let msg1 = msg1Method()
async let msg2 = msg2Method()
return await msg1 + msg2
}
}
結果 msg1Method()・msg2Method()は非同期に処理されて、msgMethod()で同期した文字列をきちんと返しています
2021-11-11 01:41:56 +0000 2021-11-11 01:42:06 +0000 msg1msg2
おまけ
iOS13でも動作する?って噂があったので実際に試してみました。 おもいっきり
only available in iOS 15.0 or newer
とエラーになりました。やっぱりiOS15以降でしか使えないってことでした。残念
