- 投稿日:2020-02-14T22:05:03+09:00
SwiftUIのおすすめ参考サイトまとめ
無料
Hacking with swift
https://www.hackingwithswift.com/quick-start/swiftui
Medium(一部有料)
https://medium.com/tag/swiftui
Liquidcoder
Kavsoft
https://kavsoft.tech/index.html
https://www.reddit.com/r/SwiftUI/
有料
DesignCode
https://designcode.io/swiftui?promo=learnswiftui
動画
Hacking with swift
https://www.youtube.com/channel/UCmJi5RdDLgzvkl3Ly0DRMlQ
Kavsoft
https://www.youtube.com/channel/UCsuV4MRk_aB291SrchUVb4w/playlists
DesignCode
https://www.youtube.com/playlist?list=PLDaHCLWmCcQLL-3YDxnPgG8VENNUiJ0Nv
- 投稿日:2020-02-14T20:06:35+09:00
【iOS】アプリが閉じた状態でUniversal Linksでの起動をデバッグする
アプリが閉じた状態でのUniversal Links経由の起動を繰り返しデバッグしたい時のメモ
手順
EditScheme
→Wait for executable to be launched
をチェック
これによって、Runした時にアプリが自動で起動しなくなりますアプリをRun(
Wait for executable to be launched
)Terminalで以下のコマンドを実行
$ xcrun simctl terminate booted <bundle_identifier> && xcrun simctl openurl booted <url>これによって常にアプリが閉じた状態で、Universal Links経由でアプリが起動するようになります。
繰り替えし確認したい場合は2と3を繰り返すことで可能となります。解説
細かいコマンドの仕様はhelpやdocumentを参照してください。
-xcrun simctl terminate booted <bundle_identifier>
は対象のアプリをタスクキルするのと同様です
-xcrun simctl openurl booted url
はUniversal Links経由でアプリを起動します
- 投稿日:2020-02-14T19:23:28+09:00
Build React Native Custom Checkbox Component for Android and iOS
In this tutorial, we are going to learn how to create a custom Checkbox component in React Native application for Android and iOS platforms. We will learn step by step how to get Multiple checkboxes values on button click using React Native APIs.
READ MORE TO CLICKHERE
https://www.positronx.io/build-react-native-custom-checkbox-for-android-and-ios/
- 投稿日:2020-02-14T17:50:21+09:00
UITableViewCellの中のUIImageVewでアニメーションすると勝手に止まる件
UITableView
の中にUIImageView
を入れていて、そのUIImageView
にanimationImages
を指定してアニメーションを表示している時、テーブルのセルを選択するとアニメーションが止まってしまう。解決法
setSelected
,setHighlighted
をオーバーライドしてその中でアニメーションを再開する。class MyCell: UITableViewCell { override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) restartAnimatingIfNeeded() } override func setHighlighted(_ highlighted: Bool, animated: Bool) { super.setHighlighted(highlighted, animated: animated) restartAnimatingIfNeeded() } func restartAnimatingIfNeeded() { if imageView.animationImages != nil { imageView.startAnimating() } } }少々無理やりな気がするけど…あと最初のフレームから再生されてしまうのも気になる…
- 投稿日:2020-02-14T17:17:41+09:00
【iOS】今すぐ始める自動テスト(XCTest, XCUITest)
iOSアプリのテストを始めたい、やったほうが良いのはわかる、 けど…
という方のための、
公式フレームワークXCTestとXCUITestで自動テストを始めるまでの記事です。セットアップ
※ 既に「アプリ名+Tests/UITests」のグループやTARGETSがある場合は不要
プロジェクト作成時に「include Unit/UI Tests」にチェック
もしくはプロジェクト作成後、File→New→Target→
Unit/UI Testing Bundleを追加して
XCTest
テストクラスの作成
まず、Testsグループ内にUnitTestクラスを作成。
↓選択
すると以下のような雛形が出来る。import XCTest class QiitaTest: XCTestCase { override func setUp() { // Put setup code here. This method is called before the invocation of each test method in the class. } override func tearDown() { // Put teardown code here. This method is called after the invocation of each test method in the class. } func testExample() { // This is an example of a functional test case. // Use XCTAssert and related functions to verify your tests produce the correct results. } func testPerformanceExample() { // This is an example of a performance test case. self.measure { // Put the code you want to measure the time of here. } } }[1] setUp
テスト開始時に動く。共通の準備等に使う。
[2] testExample
名前がtest
から始まる関数がテスト扱いとなって実行される。
[3] tearDown
テスト終了時に動く。共通の確認等に使う。例:test1(), test2()を書いた場合
setUp() > test1() > tearDown() > setUp() > test2() > tearDown()
の順で動く。Membershipの登録
他のファイルのクラスの呼び出しにはMembershipの登録が必要。
例えばLangというクラスを呼ぶ場合、アプリ内の該当ファイルを選択し…
Testsにチェックを入れる。
この操作は複数ファイルを選択して同時に適用できる。グループ単位はできませんでした。テストの実行
アプリ内のコードを実行し、XCTAssert〜系メソッドでチェックする。
※以下のコード例ではアプリ特有のクラスが出てきますが、要は関数の呼び出し結果が期待値と一致するかをチェックする単体テストです。import XCTest class ProductIdCoderServiceTests: XCTestCase { override func setUp() {} override func tearDown() {} func testEncodeLesson() { let lesson = Lesson(lang: Lang(rawValue: "ja")!, courseId: 3, lessonId: 6) let coderService = ProductIdCoderService() let productId = coderService.encodeLesson(lesson) XCTAssertEqual(productId, "ja.course.3.lesson.6") } func testDecodeLesson() { let productId = "ja.course.3.lesson.4" let coderService = ProductIdCoderService() let lesson: Lesson = coderService.decodeLesson(productId)! XCTAssertEqual(lesson.lang.rawValue, "ja") XCTAssertEqual(lesson.courseId, 3) XCTAssertEqual(lesson.lessonId, 4) } }テスト別に開始ボタンを押すか、Cmd+U等でテストを実行。
通るとこのように表示される。
XCUITest
アプリを起動し、外から操作するイメージで行われるテスト。
実際のアプリに近い画面操作をテストできるが、画面に出てこない内部情報を扱うのは難しい。テストクラスの作成
まず、UITestsグループ内にUITestクラスを作成。
↓選択
すると以下のような雛形ができる。import XCTest class QiitaUITest: XCTestCase { override func setUp() { // Put setup code here. This method is called before the invocation of each test method in the class. // In UI tests it is usually best to stop immediately when a failure occurs. continueAfterFailure = false // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. XCUIApplication().launch() // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. } override func tearDown() { // Put teardown code here. This method is called after the invocation of each test method in the class. } func testExample() { // Use recording to get started writing UI tests. // Use XCTAssert and related functions to verify your tests produce the correct results. } }XCTestに比べ continueAfterFailure と XCUIApplication() が追加されている。
continueAfterFailure = false
どこかで失敗した時にそこでテストを打ち切るという設定。UIテストは多大な時間をかけて流れを追うものなので、そうするのがオススメというもの。
XCUIApplication().launch()
アプリを起動するという操作。Membershipの登録
XCTestと同じなので割愛。
ライブラリ探索を追加
UITests => Build Settings => Runpath Search Pathsに
$(FRAMEWORK_SEARCH_PATHS)
を追加
この方法がスタンダードかは不明だが、バンドル“appUITests”は、壊れているか必要なリソースがないため読み込めませんでした。 バンドルを再インストールしてください。
とエラーが出る場合に有効。
テストの実行
テストの実行方法自体はXCTestと同じだが、XCUIApplicationやXCUIElementを使ってコードからアプリを操作する必要がある。
操作法の一部を紹介します。class PurchaseLessonUITests: XCTestCase { var app: XCUIApplication! override func setUp() { continueAfterFailure = false app = XCUIApplication() } override func tearDown() {} func testPurchase() { app.launch() addUIInterruptionMonitor(withDescription: "アラート検知") { (alert) -> Bool in alert.buttons["OK"].tap() return true } app.buttons["purchaseButton"].tap() XCTAssert(XCUIApplication().alerts["お買い上げありがとうございます"].exists) } }割り込みへの予約
発生タイミングが定かでないものに対してアクションを予約しておける。
アラートを検知した時に即OKボタンを押す例。addUIInterruptionMonitor(withDescription: "アラート検知") { (alert) -> Bool in alert.buttons["OK"].tap() return true }要素を探す
accessibilityIdentifierをアプリケーション内で設定しておくと、
@IBOutlet weak var purchaseButton: UIButton! { didSet { purchaseButton.accessibilityIdentifier = "purchaseButton" } }テストでこのように探し出してタップなどができる。
app.buttons["purchaseButton"].tap()UIによるが、単にタイトル等で探せる場合もある。
// `exists`を呼び出して`XCTAssert`に放り込めば存在をチェックできる XCTAssert(XCUIApplication().alerts["お買い上げありがとうございます"].exists)XCTestとXCUITestの基本は以上です。
応用:XCUITestで軽めにモックを挟む
テストにおいて、状況設定に応じたコードを走らせたい場合があると思います。
XCUITestはアプリを外から操作するイメージで行われるため、内部の状態把握やコントロールが少し難しいです。
XCUIApplicationのlaunchArguments
とlaunchEnvironment
を使ってアプリケーション内のコードを切り替える例を紹介します。Argumentsを使ってテスト用モックを挟む
https://developer.apple.com/documentation/xctest/xcuiapplication/1500477-launcharguments
まず、launchArgumentsにテストである旨を追加。
import XCTest class UITests: XCTestCase { var app: XCUIApplication! override func setUp() { continueAfterFailure = false app = XCUIApplication() app.launchArguments.append("UI_TEST") // ← これ } override func tearDown() {} func testHoge() { app.launch() // Hogeをテストするコード } }アプリケーション側にテスト用のクラスを追加で用意し、
HogeService.swiftprotocol HogeService { // ..略 } class DefaultHogeService { // 本来使用するクラス // ..略 } class TestHogeService { // テスト用クラスを追加 // ..略 }例えばインスタンス生成時に必要に応じて選択するように変更する。
func hogeService() -> HogeService { if ProcessInfo.processInfo.arguments.contains("UI_TEST") { // テストかを判定 return TestHogeService() } else { return DefaultHogeService() } }ここはアプリ本体への追記になってしまうが、致し方ない。
これでテスト時はTestHogeService()
が使われるようになった。Environmentsを使って分岐を制御する
https://developer.apple.com/documentation/xctest/xcuiapplication/1500427-launchenvironment
次は、HogeServiceが成功・失敗した後それぞれのケースもテストしたい。
launchEnvironmentに命令を書くことにする。import XCTest class UITests: XCTestCase { var app: XCUIApplication! override func setUp() { continueAfterFailure = false app = XCUIApplication() app.launchArguments.append("UI_TEST") } override func tearDown() {} func testHogeSuccess() { app.launchEnvironment["HOGE_SERVICE"] = "success" // HogeServiceの挙動を成功と指定 app.launch() // Hogeが成功した後の挙動が正しいかをテストするコード } func testHogeFailure() { app.launchEnvironment["HOGE_SERVICE"] = "failure" // HogeServiceの挙動を失敗と指定 app.launch() // Hogeが失敗した後の挙動が正しいかをテストするコード } }TestHogeServiceで読み取り、分岐させる。
protocol HogeService { func fuga() } class DefaultHogeService { // ..略 } class TestHogeService: HogeService { func fuga() { if ProcessInfo.processInfo.environment["HOGE_SERVICE"] == "success" { // 成功時にやること } else if ProcessInfo.processInfo.environment["HOGE_SERVICE"] == "failure" { // 失敗時にやること } else { fatalError() } } }以上です。お役に立てば幸いです。
- 投稿日:2020-02-14T16:47:11+09:00
Xcodeでnib(xib)のロードが劇遅だった話
はじめに
昔は速かったXcode、数年前からnibのロードが劇的に遅くなった。
1ファイル10秒とかかかる。
なんだそれー!!!ありえねー
Appleふざけんなー色々試行錯誤の結果、劇的に改善できたー
やってみた
その方法は
~/Library/Caches/以下を全部削除。
一応Xcodeは再起動した。のみ。他にも色々試したけど、多分これで決まり。
macOSをセーフブートすると、キャッシュも削除されるって話なので、多分それでもいける。まとめ
よかったー
作業効率が劇的に良くなった。
でもたまにxcodeは10秒程度フリーズするのは直らず
まあたまになので、問題無い。短か。
でもこれで全部。
- 投稿日:2020-02-14T16:41:27+09:00
UIViewController�の全面にUITableViewを設置する場合、`UITableView`を基底viewにしてしまえばよさそう
概要
UIViewController
の全面にUITableView
を設置する場合、UITableView
を基底viewにしてしまえばよさそう- メリット
- 管理するviewが一つ減る
- AutoLayoutの制約が不要になる
UIViewController
のview
インスタンス(基底view)がUITableView
のインスタンスになるスクリーンショット
手順
- Storyboardに
UIViewController
を設置1.
で設置したUIViewController
の基底ViewをStoryboard上で削除するUITableView
を選択しドラッグアンドドロップで基底ViewとしてUIViewController
に設置その他
- 基本的にどんなViewも基底viewに設定できる
UITableViewCell
やUINavigationItem
など、他のView上に設置されるようなViewも設定できる
- 投稿日:2020-02-14T12:10:34+09:00
iOSでホーム画面に表示されるアプリ名はどこで決まるのか?変更するには?
iOSでホーム画面に表示されるアプリ名はどこで決まるのか?変更するにはどうすればよいか?を整理してまとめました。
確認環境
- Xcode 11.3.1
- iOS 13.3
結論
- アプリ名は Info.plist ファイルの CFBundleDisplayName で決まる
- アプリ名を変更するには、その CFBundleDisplayName の値を変更すればよい
- アプリ名をローカライズするには、対象言語毎に InfoPlist.strings を用意する
- Build Configuration に応じてアプリ名を切り替えるには、 User-Defined Setting を使うと良い
- Build Configuration に応じてアプリ名を切り替えつつローカライズするには、ビルド時にがんばるしかなさそう
ホーム画面のアプリ名はどこで決まるのか?
新規プロジェクトを作成して確認していく。
プロジェクト作成時に Product Name を入力する。
以下が先ほど入力した Product Name になっていることがわかる。
- プロジェクトファイル名
- ターゲット名
- ターゲット設定の General タブにある Display Name
このうち、最後の Display Name がホーム画面に表示されるアプリ名となる。
アプリ名を変更するには?
この Display Name を変更してみると、プロジェクトファイルの
PRODUCT_NAME
が変わる。また、変更前のPRODUCT_NAME
はターゲット名TARGET_NAME
であったこと、そしてPRODUCT_NAME
がアプリケーションファイル (.app) 名になることがわかる。
アプリケーションファイル名が変わるのは気持ち悪いので
PRODUCT_NAME
を変更しないようにしたい。そんな時のために Bundle display name という設定がある。これはターゲット設定の Info タブで追加できるので、追加してアプリ名を入力してみる。
General タブの Display Name は入力した名前に変わった。ホーム画面上でも変わっている。
その正体は Info.plist ファイルの CFBundleDisplayName キー。公式ドキュメントにも ホーム画面で表示される名前 という説明がある。
The user-visible name for the bundle, used by Siri and visible on the iOS Home screen.
アプリ名をローカライズするには?
アプリ名は Info.plist の CFBundleDisplayName で決まるので、これをローカライズしたい。その方法は公式ドキュメント About Information Property List Files の Localizing Property List Values という章で説明されているとおり、InfoPlist.strings という Info.plist 国際化用の strings リソースファイルを用意する。
Localized values are not stored in the Info.plist file itself. Instead, you store the values for a particular localization in a strings file with the name InfoPlist.strings.
InfoPlist.strings ファイルの作成方法、記述方法、言語別に用意する方法は、通常の文字列リソースファイルをローカライズする場合と同様。プロジェクト設定でローカライズ対象言語を設定する。
InfoPlist.strings ファイルをプロジェクトに追加する。
言語別に CFBundleDisplayName を設定する。
Build Configuration に応じてアプリ名を切り替えるには?
ターゲット設定の Build Settings タブにある設定項目は Build Configulation (Debug, Releaseなど) ごとに値を変更できる。ここには User-Defined というユーザー定義の設定を追加することができる。
ここに、例えば
BUNDLE_DISPLAY_NAME
のようなキーを追加し、Build Configuration ごとにアプリ名となる値を入力する。
そして、Info.plist の Bundle display name の部分に、先ほどの
BUNDLE_DISPLAY_NAME
を参照させる。
General タブの Display Name は、Run を実行する Build Configuration のものが表示されている。
Build Configuration に応じてアプリ名を切り替えつつローカライズするには?
上記の手段では、最終的に InfoPlist.strings の方のアプリ名が優先されてしまい、両立することができない。Xcode に用意されている仕組みでは、これを両立する手段はなさそう。
ただ、開発の要件上、このような対応が必要になったため、次のようにして力技で対応してみた。
まず、全言語の InfoPlist.strings に対して、
CFBundleDisplayName_{Build Configuration}
というキーで、Build Configuration ごとにアプリ名を定義する。en.lproj/InfoPlist.strings"CFBundleDisplayName_Debug" = "HogeDebug"; "CFBundleDisplayName_Release" = "HogeRelease";ja.lproj/InfoPlist.strings"CFBundleDisplayName_Debug" = "ほげデバッグ"; "CFBundleDisplayName_Release" = "ほげリリース";そして Build Phases の Run Script で全言語の InfoPlist.strings に対して、ビルド対象の Build Configuration に該当する
CFBundleDisplayName_{Build Configuration}
をCFBundleDisplayName
に置換する。例えば Release の場合は `CFBundleDisplayName_Release
が置換対象となる。置換sed -i "s/\"CFBundleDisplayName_$CONFIGURATION\"/\"CFBundleDisplayName\"/" path/to/InfoPlist.stringsen.lproj/InfoPlist.strings"CFBundleDisplayName_Debug" = "HogeDebug"; "CFBundleDisplayName" = "HogeRelease";ja.lproj/InfoPlist.strings"CFBundleDisplayName_Debug" = "ほげデバッグ"; "CFBundleDisplayName" = "ほげリリース";
- 投稿日:2020-02-14T11:39:43+09:00
UIAppearanceの優先度
UIAppearance の基礎
これですべての
UILabel
のtextColor
が茶色に変わります。
(現実的にはUILabel
のtextColor
をUIAppearance
で設定するのは稀だと思いますが、あくまでサンプルとして)UILabel.appearance().textColor = .brown特定のView上にあるUILabelのtextColorを変える
View階層が1つだけの時
すべての
ViewA
上のUILabel
を変えたいなら、こんなふうに書けば良いですね。UILabel.appearance(whenContainedInInstancesOf: [ViewA.self]).textColor = .redView階層が2つ以上の時
SampleViewController
上のViewA
上のUILabel
だけを変えたい時など、
階層が2つ以上の時は、対象となるViewから親View/ViewControllerを順に配列の要素に加えます。UILabel.appearance(whenContainedInInstancesOf: [ViewA.self, SampleViewController.self]).textColor = .red優先度
例えば以下のような階層構造の時
SampleViewController ┣ UILabel ┣ ViewA ┃ ┗ UILabel ┗ ViewB ┗ ViewA ┗ UILabelこのように設定するとどうなるでしょうか。
UILabel.appearance().textColor = .brown UILabel.appearance(whenContainedInInstancesOf: [SampleViewController.self]).textColor = .magenta UILabel.appearance(whenContainedInInstancesOf: [ViewA.self]).textColor = .blue UILabel.appearance(whenContainedInInstancesOf: [ViewB.self, SampleViewController.self]).textColor = .redこの場合、より外側の要素での指定が優先され、以下のように解決されます。
ViewA
上のUILabel
はblue
にはならず、
SampleViewController
上のUILabel
をmagenta
にする処理が優先されます。
SampleViewController
上のViewB
のUILabel
をred
にする処理は、指定した順に解決され問題なく実行されます。参考