- 投稿日:2020-08-30T23:35:06+09:00
Swift で日本語を含む URL を扱う
日本語を含む文字列を URL に変換する時に値が nil になることがあると思います。これは、URI において使用できない文字列を URL として変換しようとしている時に発生します。Google などの検索バーでは、このような使用できない文字列を自動でパーセントエンコーディングしているので正常に検索を行うことができます。自分もたまに、エンコーディングを忘れてしまうのでこれを機に気をつけて行こうかと思います。
文字列をエンコード
今回は楽天市場の商品 URL を取得するという設定でエンコーディングを行っていきます。
let itemString = "スコーピオン" let itemEncodeString = itemString.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) let urlString = "https://search.rakuten.co.jp/search/mall/\(itemEncodeString!)" print("itemEncodeString: \(itemEncodeString!)") // itemEncodeString: %E3%82%B9%E3%82%B3%E3%83%BC%E3%83%94%E3%82%AA%E3%83%B3 print("Result URL: \(URL(string: urlString)!.absoluteString)") // Result URL: https://search.rakuten.co.jp/search/mall/%E3%82%B9%E3%82%B3%E3%83%BC%E3%83%94%E3%82%AA%E3%83%B3上記のようにエンコードすることができました?
.addingPercentEncoding(withAllowedCharacters: CharacterSet)
にセットするCharacterSetですが、今回の場合は URL の Path にセットされる文字列に日本語が含まれている可能性があったので.urlPathAllowed
という CharacterSet を使用しています。URL String 全体に対してエンコーディングをかける場合は、.urlFragmentAllowed
もしくは.urlQueryAllowed
を使用すればそれなりに正しくは動きますが、個人的にはお勧めしません。理由としては、それぞれの CharacterSet で守備範囲としている URL コンポーネントが違うからです。例で言うと下記のように、パスの文字列で?
を含む URL String 全体を.urlQueryAllowed
を使ってエンコードした場合、?
は文字列とみなされずエンコードされません。つまり、結果として取得した URL が正確でない可能性があります。そのため、使う用途に分けて URL コンポーネントそれぞれをエンコードするのがベストでしょう。let urlString = "https://search.rakuten.co.jp/search/mall/スコーピオ?ン" let encodeUrlString = urlString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) print("Result URL: \(URL(string: encodeUrlString!)!.absoluteString)") // Result URL: https://search.rakuten.co.jp/search/mall/%E3%82%B9%E3%82%B3%E3%83%BC%E3%83%94%E3%82%AA?%E3%83%B3URL コンポーネント別の CharacterSet 一覧は下記になります。
参考
- 投稿日:2020-08-30T22:22:47+09:00
UITableViewにUIButtonを追加する
UITableViewの追加方法と、Cellにボタンを追加する方法を記載します。
UITableViewの作成
UITableView(frame: CGRect, style: UITableView.Style)でUITableViewを作成します。
CGRect(x: CGFloat, y: CGFloat, width: CGFloat, height: CGFloat)でテーブルのサイズを指定し、
self.view.addSubview()でテーブルを画面に追加します、var tableView: UITableView = UITableView() override func viewDidLoad() { super.viewDidLoad() // 画面の横の長さ let width = self.view.frame.size.width // 画面の縦の長さ let height = self.view.frame.size.height tableView = UITableView(frame: self.view.frame, style: UITableView.Style.grouped) tableView.dataSource = self tableView.delegate = self tableView.sizeToFit() // tableViewのサイズ指定 tableView.frame = CGRect(x:0, y:50, width:Int(width), height: Int(height)) // 追加 self.view.addSubview(tableView) }UITableViewを作成するばあ、row数とsection数の指定も必要になります。
// section数 public func numberOfSections(in tableView: UITableView) -> Int { return 1 } // row数 func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return 9 }Cellの中身を追加
UITableViewのCellを内容を追加します。
Cellに、UItextField、UIButton等の追加ができます。
追加時にタグにindexPathを入れることで、選択時にどこを選択しているのか判断できます。// Cellの高さ func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { return 50 } // Cellの内容 func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { var cell = tableView.dequeueReusableCell(withIdentifier: "Cell") if cell == nil { cell = UITableViewCell(style: .value1, reuseIdentifier: "Cell") } else { self.view.willRemoveSubview(cell!) cell = UITableViewCell(style: .value1, reuseIdentifier: "Cell") } let width = self.view.frame.size.width // UITextField let textfi = UITextField() textfi.delegate = self textfi.tag = indexPath.row textfi.frame = CGRect(x:50, y:0, width:width - 50, height: 50) cell?.addSubview(textfi) // UIButton let addbutton = UIButton() addbutton.tag = indexPath.row // 押下時動作 addbutton.addTarget(self, action: #selector(buttonEvemt), for: UIControl.Event.touchUpInside) addbutton.frame = CGRect(x:0, y:0, width:50, height: 50) cell?.addSubview(addbutton) return cell! }Cellの選択時の動作
Cellを選択してときの動作を追加できます。
UIButton、UITextField等は画面に追加したときと同じ用に記載すればいいですが、
どのCellを選択しているかわからないため、tagに入れたindexPath.rowでどれを選択しているか
判断できるようにする必要があります。// Cell選択時動作 func tableView(_ table: UITableView, didSelectRowAt indexPath: IndexPath){ } // UIButtonを選択した時 @objc func buttonEvemt(_ sender: UIButton) { print("buttonTag", sender.tag) } // UITextFieldを選択した時 func textFieldDidBeginEditing(_ textField: UITextField) { print("start",textField) print("tag",textField.tag) } // UITextFieldを離した時 func textFieldDidEndEditing(_ textField: UITextField) { print("end",textField) print("tag",textField.tag) }
- 投稿日:2020-08-30T20:36:49+09:00
謎のinsertArrangedSubviewでクラッシュする問題
筆者が開発しているiOSのアプリで、App Store Connect経由でクラッシュログが繰り返し送られてくるものの、再現方法も解決方法もずっと謎であった不具合が解決したのでメモとして残しておきます。
問題の内容
送られてくるクラッシュログは以下のようなものでした。
Last Exception Backtrace: 0 CoreFoundation 0x18d2d75b4 __exceptionPreprocess + 220 (NSException.m:199) 1 libobjc.A.dylib 0x1a12c742c objc_exception_throw + 60 (objc-exception.mm:565) 2 CoreFoundation 0x18d1d3aac +[NSException raise:format:] + 112 (NSException.m:155) 3 UIKitCore 0x19001b6d4 -[UIStackView insertArrangedSubview:atIndex:] + 184 (UIStackView.m:103) 4 UIKitCore 0x18f1723e4 -[_UIButtonBar _layoutBar] + 2576 (UIButtonBar.m:555) 5 UIKitCore 0x18f175b38 __42-[_UIButtonBarStackView updateConstraints]_block_invoke + 52 (UIButtonBar.m:1293) 6 UIKitCore 0x190112468 +[UIView(Animation) performWithoutAnimation:] + 104 (UIView.m:13927) 7 UIKitCore 0x18f175ad8 -[_UIButtonBarStackView updateConstraints] + 120 (UIButtonBar.m:1292) 8 UIKitCore 0x19003458c -[UIView(AdditionalLayoutSupport) _sendUpdateConstraintsIfNecessaryForSecondPass:] + 484 (NSLayoutConstraint_UIKitAdditions.m:0) 9 UIKitCore 0x190034a44 -[UIView(AdditionalLayoutSupport) _updateConstraintsIfNeededCollectingViews:forSecondPass:] + 948 (NSLayoutConstraint_UIKitAdditions.m:4409) 10 UIKitCore 0x190034924 -[UIView(AdditionalLayoutSupport) _updateConstraintsIfNeededCollectingViews:forSecondPass:] + 660 (NSLayoutConstraint_UIKitAdditions.m:4390) 11 UIKitCore 0x190034924 -[UIView(AdditionalLayoutSupport) _updateConstraintsIfNeededCollectingViews:forSecondPass:] + 660 (NSLayoutConstraint_UIKitAdditions.m:4390) 12 CoreAutoLayout 0x1a154c060 -[NSISEngine withBehaviors:performModifications:] + 88 (NSISEngine.m:1917) 13 UIKitCore 0x1900351f4 __100-[UIView(AdditionalLayoutSupport) _updateConstraintsIfNeededWithViewForVariableChangeNotifications:]_block_invoke + 116 (NSLayoutConstraint_UIKitAdditions.m:4455) 14 UIKitCore 0x190033b30 -[UIView(AdditionalLayoutSupport) _withUnsatisfiableConstraintsLoggingSuspendedIfEngineDelegateExists:] + 128 (NSLayoutConstraint_UIKitAdditions.m:4154) 15 UIKitCore 0x190034db4 -[UIView(AdditionalLayoutSupport) _updateConstraintsIfNeededWithViewForVariableChangeNotifications:] + 188 (NSLayoutConstraint_UIKitAdditions.m:4454) 16 UIKitCore 0x190035cac -[UIView(AdditionalLayoutSupport) _updateConstraintsAtEngineLevelIfNeededWithViewForVariableChangeNotifications:] + 436 (NSLayoutConstraint_UIKitAdditions.m:4707) 17 UIKitCore 0x19010c028 -[UIView _updateConstraintsAsNecessaryAndApplyLayoutFromEngine] + 404 (UIView.m:12304) 18 UIKitCore 0x18f2659f8 -[UIToolbar layoutSubviews] + 56 (UIToolbar.m:712) 19 UIKitCore 0x19011f7dc -[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 2500 (UIView.m:17460) 20 QuartzCore 0x190627464 -[CALayer layoutSublayers] + 296 (CALayer.mm:10129) 21 QuartzCore 0x190627920 CA::Layer::layout_if_needed(CA::Transaction*) + 524 (CALayer.mm:9996) 22 QuartzCore 0x19063bd48 CA::Layer::layout_and_display_if_needed(CA::Transaction*) + 144 (CALayer.mm:2478) 23 QuartzCore 0x1905833e4 CA::Context::commit_transaction(CA::Transaction*, double, double*) + 416 (CAContextInternal.mm:2380) 24 QuartzCore 0x1905ae6dc CA::Transaction::commit() + 732 (CATransactionInternal.mm:449) 25 QuartzCore 0x1905afa3c CA::Transaction::observer_callback(__CFRunLoopObserver*, unsigned long, void*) + 96 (CATransactionInternal.mm:925) 26 CoreFoundation 0x18d253454 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 36 (CFRunLoop.c:1799) 27 CoreFoundation 0x18d24d868 __CFRunLoopDoObservers + 576 (CFRunLoop.c:1912) 28 CoreFoundation 0x18d24de18 __CFRunLoopRun + 1056 (CFRunLoop.c:2953) 29 CoreFoundation 0x18d24d4cc CFRunLoopRunSpecific + 600 (CFRunLoop.c:3242) 30 GraphicsServices 0x1a3c31820 GSEventRunModal + 164 (GSEvent.c:2259) 31 UIKitCore 0x18fbf2a28 -[UIApplication _run] + 1072 (UIApplication.m:3270) 32 UIKitCore 0x18fbf8104 UIApplicationMain + 168 (UIApplication.m:4739) 33 KifuBox 0x102e6c2b0 main + 68 (WaitingViewController.swift:12) 34 libdyld.dylib 0x18cf14e60 start + 4厄介な点の1つとして、自分が直接書いたソースコードはスタック中にはなく、何をトリガに起きているのかわからないことがありました。
最終的にはUIStackView
のinsertArrangedSubview
で落ちており、スタックの途中には_UIButtonBarStackView
などが出ているので、多分UIBarButtonItem
に関連する何かだとは思うのですが、どこで問題が起きているのか全くわかりませんでした。発生の傾向を見ると、iOS13.4以降でしか起きていないようで、それがiOS13.4以降でしか発生しない現象なのか、たまたま時期的な関係でそう見えているのかはよくわかりませんでした。
原因と解決方法
結論としては、Apple Developers Forumにあった以下の記事が当たりでした。
https://developer.apple.com/forums/thread/130038上記記事では
UIAlertController
だそうですが、筆者のアプリでは画面下部のUIToolbar
にUIBarButtonItem
を追加する際に、同じオブジェクトが含まれていると、iOS13.4以降ではクラッシュするという問題でした。(iOS13.3以前では同じコードでも問題は発生しない)MyViewController.swiftself.setToolbarItems([filterButton, fixSpace1, dummyIcon, flexSpace1, filterStateButton, flexSpace2, dummyIcon, fixSpace2, dummyIcon], animated: true)上記のソースコードが問題で、ツールバーにボタンを並べる際のレイアウト調整のために、見えないダミーのボタン(
dummyIcon
)を3つ追加していたのですが、その3つが同じオブジェクトであったためにクラッシュしていました。以下のように3つ別のオブジェクトにすることで解決しました(オブジェクトの生成コード等は省略)
MyViewController.swiftself.setToolbarItems([filterButton, fixSpace1, dummyIcon1, flexSpace1, filterStateButton, flexSpace2, dummyIcon2, fixSpace2, dummyIcon3], animated: true)手がかりが
insertArrangedSubview
くらいしかなくて難航したので、少しでもネット上で情報が見つかるように記事を書いてみました。
- 投稿日:2020-08-30T19:55:28+09:00
UITableViewのinsertRows()でEXC_BAD_ACCESSが発生した件
筆者が開発しているiOSのアプリで、App Store Connect経由でクラッシュログが繰り返し送られてくるものの、再現方法も解決方法もずっと謎であった不具合が解決したのでメモとして残しておきます。
問題の内容
送られてくるクラッシュログは以下のようなものでした。
Thread 0 name: Thread 0 Crashed: 0 UIKitCore 0x000000018fde4bd0 __46-[UITableView _updateWithItems:updateSupport:]_block_invoke + 60 (UITableView.m:3826) 1 UIKitCore 0x000000018fde4bd0 __46-[UITableView _updateWithItems:updateSupport:]_block_invoke + 60 (UITableView.m:3826) 2 UIKitCore 0x000000018fde4cdc __46-[UITableView _updateWithItems:updateSupport:]_block_invoke.1109 + 212 (UITableView.m:3911) 3 UIKitCore 0x000000018fde4040 -[UITableView _updateWithItems:updateSupport:] + 2036 (UITableView.m:3919) 4 UIKitCore 0x000000018fddcfe4 -[UITableView _endCellAnimationsWithContext:] + 11128 (UITableView.m:2387) 5 UIKitCore 0x000000018fdf5ad8 -[UITableView _updateRowsAtIndexPaths:withUpdateAction:rowAnimation:usingPresentationValues:] + 628 (UITableView.m:7767) 6 UIKitCore 0x000000018fdf5bec -[UITableView insertRowsAtIndexPaths:withRowAnimation:] + 172 (UITableView.m:7776) 7 KifuBox 0x000000010087fc8c GameListViewController.addGame(game:) + 1140 (GameListViewController.swift:460) 8 KifuBox 0x00000001008826bc addGame + 16 (GameListViewController.swift:436) 9 KifuBox 0x00000001008826bc GameListViewController.newPushed() + 1804 (GameListViewController.swift:628) 10 KifuBox 0x000000010088329c @objc GameListViewController.newPushed() + 28 (<compiler-generated>:0) (以下略)
UITableView
で作成した一覧で新規に行を追加した場合に、insertRows(at:with:)
でEXC_BAD_ACCESSが発生するというものです。
送られてくるクラッシュログの発生環境から見ると、iPadでのみ発生し、iPhoneでは発生していませんでした。OSのバージョンはあまり関係ないようで、11系〜13系まで幅広く発生していました。発生し始めた時期に行った修正内容でinsertRows近辺のものがないか調べましたが、あまり関係のありそうなものがありませんでした。
ネットでもいろいろ検索してみたところ、テーブルの更新部分を
beginUpdates()
〜endUpdates()
で囲うと良いという情報があったのでやってみましたが、効果はなく、それ以外に有効な情報は見つかりませんでした。再現条件と解決方法
それから紆余曲折を経て、やっと再現方法がわかりました。このアプリは
UISplitViewController
を使用して、プライマリー部分にリストを、セカンダリー部分にそのコンテンツを表示するようなものになっています。
iPadではリストとコンテンツが同時に表示されるので、わかりやすいように表示されているコンテンツに対応するリストの行を選択表示していました。
UITableView
で編集モードに入ると、選択状態が解除されてしまうため、編集モードから出た際に行をselectRow(at:animated:scrollPosition:)
で再選択するロジックを、問題が起こり始めた時期に追加していたのですが、このロジックに誤りがあり、既になくなっている行をselectRow
してしまうケースがありました。確実に再現できた例でいくと、リストに1行しかない状態で編集モードに入り、行を削除して編集モードから抜けると、既に行は1つもないにもかかわらず、row=0, section=0の行を
selectRow()
してしまっていました。この状態から新規に行を追加してinsertRows(at:with:)
を行うとクラッシュする、という現象でした。このような理由だったので、
selectRow()
が正しくなるように修正した結果、クラッシュしないようになりました。まとめ
UITableView
で存在しない行がselectRow()
されている状態でinsertRows(at:with:)
を行うとクラッシュする場合があります。
あまり発生しにくいケースだとは思いますが、ネット上にもほとんど情報が見つからなかったので、一応記事にしておきました。
- 投稿日:2020-08-30T13:11:26+09:00
App Attest APIの概要
※2020/9/1執筆→11/16全体公開(忘れていました)
App Attest APIは、iOS 14以上で使用できる、DeviceCheckの一種です。
たとえばアプリがハックされると、チートや広告の削除、有料機能の不正利用などが行われます。
それを防ぐため、デバイスで特殊な暗号鍵を生成し、サーバーが機密データにアクセスする際にAppleの認証を受けることで、アプリが正当なことを証明できます。アプリの実装方法
こちら参照。DCAppAttestServiceを用いて以下のように実装します。
1. API利用可否の判定
guard DCAppAttestService.shared.isSupported else { log.error("Unsupported") return } // サービス利用可2. 暗号鍵の生成
DCAppAttestService.shared.generateKey { keyId, error in guard error == nil, let keyId = keyId else { log.error(error.debugDescription ?? "Unknown error") return } // KeyId生成完了 }3. チャレンジの要求
サーバー(Your server)にKeyIdを送り、チャレンジ(サーバーが決めたランダム値)を受け取ります。
let challenge = // サーバーから受け取る4. keyIdとチャレンジを使って認証
let hash = Data(SHA256.hash(data: challenge)) DCAppAttestService.shared.attestKey(keyId, clientDataHash: hash) { attestation, error in if let error = error { log.error(error.debugDescription) } else if let attestation = attestation { // 認証OK/NG判定 } else { log.error("Attestation is null") } }ここまでは、アプリ起動時に1回だけ行えばよい
5. アプリの正当性の主張
以後、サーバーリクエスト毎に、アプリの正当性を主張します。
今回はチャレンジとサーバーリクエスト内容を組み合わせたハッシュを作成します。
let challenge = // サーバーから受け取る let request = [ "action": "getUser", "userId": "1234", "challenge": challenge ] guard let clientData = try? JSONEncoder().encode(request) else { return } let hash = Data(SHA256.hash(data: clientData)) service.generateAssertion(keyId, clientDataHash: hash) { assertion, error in if let error = error { log.error(error.debugDescription) } else if let assertion = assertion { // 正当性OK/NG判定 } else { log.error("Assertion is null") } }サーバーの実装方法
こちら参照。やることは以下
・(Your Appに対して)チャレンジの払い出しと保持
・(App attestに対して)認証オブジェクトの生成※サーバーの実装方法はよくわかっておらず、調査中
DeviceCheckについて
App Attest単体では、不正を行ったデバイスの特定はできないので、特定したい場合DeviceCheckと併用します。
DeviceCheckは、こちらの記事が分かりやすいです。参考
- 投稿日:2020-08-30T09:12:46+09:00
[Xcode] [Swift] 実務的Tips Unwind Segueで2つ以上前の画面に一気に戻って画面を再更新する
今日からでもすぐに取り入れられて、
- コードをよりクリーンにできる、とか
- 工数を削減できる、とか
そんなTipsを紹介していく記事シリーズです。
「知らなかった」「気づかなかった」「忘れていた」そんな誰かの役に立てば幸いです。前提環境
- Xcode 11.3.1
- Swift 5.1.3
Unwind segue
概要・使い所
- 2つ以上前の画面に一気に戻りたい時。
- 特に、戻り先の画面にて、画面更新などの処理を行いたい時に便利。
手順
- FirstViewController > SecondViewController はpush遷移です。
- SecondViewController > ThirdViewController はmodal遷移です。
やりたいことは「ThirdViewControllerからFirstViewControllerに一気に戻りたい」です。
手順1.戻り先に関数を定義する
戻り先であるFirstViewControllerに以下のようなコードを書きます。
関数名はなんでも良いです。FirstViewController.swift@IBAction func backToFirst(_ segue: UIStoryboardSegue) { print("\(segue.identifier!)") }手順2.遷移元にUnwind Segueを定義する
Storyboardの遷移元のViewControllerの"Exit"に対して、Ctrlを押しながらオブジェクトをドラッグするとUnwind Segueを定義できます。
例えば、UIButtonから"Exit"にCtrl+ドラッグするとこんなふうに上で定義した
backToFirst
が表示されますので、それを選択するとUnwind Segueを追加できます。
Unwind Segueを追加したら任意のSegue Identifierを付けます。
実行結果
ThirdViewControllerのUIButtonをtapすると、
(1) ThirdViewControllerが閉じられ、
(2) SecondViewControllerからFirstViewControllerにpopされる、
という動きになります。すなわち、「ThirdViewControllerからFirstViewControllerに一気に戻りたい」を簡単に実現できます。
また、FirstViewControllerの
backToFirst(:_)
が実行され、segue.identifier
に上で付けたfromThirdToFirst
が渡ってきます。
すなわち、backToFirst(:_)
に、画面更新など、戻り先で行うべきコードを記述することができます。蛇足
私はコードでこれらを(Unwind Segueと同程度に簡単に)実現できる方法を知りません。
もしご存知の方がいたらコメントで教えていただきたいです。
- 投稿日:2020-08-30T09:12:46+09:00
[Xcode] [Swift] 実務的Tips: Unwind Segueで2つ以上前の画面に一気に戻って画面を再更新する
今日からでもすぐに取り入れられて、
- コードをよりクリーンにできる、とか
- 工数を削減できる、とか
そんなTipsを紹介していく記事シリーズです。
「知らなかった」「気づかなかった」「忘れていた」そんな誰かの役に立てば幸いです。前提環境
- Xcode 11.3.1
- Swift 5.1.3
Unwind segue
概要・使い所
- 2つ以上前の画面に一気に戻りたい時。
- 特に、戻り先の画面にて、画面更新などの処理を行いたい時に便利。
手順
- FirstViewController > SecondViewController はpush遷移です。
- SecondViewController > ThirdViewController はmodal遷移です。
やりたいことは「ThirdViewControllerからFirstViewControllerに一気に戻りたい」です。
手順1.戻り先に関数を定義する
戻り先であるFirstViewControllerに以下のようなコードを書きます。
関数名はなんでも良いです。FirstViewController.swift@IBAction func backToFirst(_ segue: UIStoryboardSegue) { print("\(segue.identifier!)") }手順2.遷移元にUnwind Segueを定義する
Storyboardの遷移元のViewControllerの"Exit"に対して、Ctrlを押しながらオブジェクトをドラッグするとUnwind Segueを定義できます。
例えば、UIButtonから"Exit"にCtrl+ドラッグするとこんなふうに上で定義した
backToFirst
が表示されますので、それを選択するとUnwind Segueを追加できます。
Unwind Segueを追加したら任意のSegue Identifierを付けます。
実行結果
ThirdViewControllerのUIButtonをtapすると、
(1) ThirdViewControllerが閉じられ、
(2) SecondViewControllerからFirstViewControllerにpopされる、
という動きになります。すなわち、「ThirdViewControllerからFirstViewControllerに一気に戻りたい」を簡単に実現できます。
また、FirstViewControllerの
backToFirst(:_)
が実行され、segue.identifier
に上で付けたfromThirdToFirst
が渡ってきます。
すなわち、backToFirst(:_)
に、画面更新など、戻り先で行うべきコードを記述することができます。蛇足
私はコードでこれらを(Unwind Segueと同程度に簡単に)実現できる方法を知りません。
もしご存知の方がいたらコメントで教えていただきたいです。
- 投稿日:2020-08-30T09:12:46+09:00
[Xcode] [Swift] 今日からすぐ使える実務的Tipsシリーズ(2) Unwind Segue
今日からでもすぐに取り入れられて、
- コードをよりクリーンにできる、とか
- 工数を削減できる、とか
そんなTipsを紹介していく記事シリーズです。
「知らなかった」「気づかなかった」「忘れていた」そんな誰かの役に立てば幸いです。前提環境
- Xcode 11.3.1
- Swift 5.1.3
Unwind segue
概要・使い所
- 2つ以上前の画面に一気に戻りたい時。
- 特に、戻り先の画面にて、画面更新などの処理を行いたい時に便利。
手順
- FirstViewController > SecondViewController はpush遷移です。
- SecondViewController > ThirdViewController はmodal遷移です。
やりたいことは「ThirdViewControllerからFirstViewControllerに一気に戻りたい」です。
手順1.戻り先に関数を定義する
戻り先であるFirstViewControllerに以下のようなコードを書きます。
関数名はなんでも良いです。FirstViewController.swift@IBAction func backToFirst(_ segue: UIStoryboardSegue) { print("\(segue.identifier!)") }手順2.遷移元にUnwind Segueを定義する
Storyboardの遷移元のViewControllerの"Exit"に対して、Ctrlを押しながらオブジェクトをドラッグするとUnwind Segueを定義できます。
例えば、UIButtonから"Exit"にCtrl+ドラッグするとこんなふうに上で定義した
backToFirst
が表示されますので、それを選択するとUnwind Segueを追加できます。
Unwind Segueを追加したら任意のSegue Identifierを付けます。
実行結果
ThirdViewControllerのUIButtonをtapすると、
(1) ThirdViewControllerが閉じられ、
(2) SecondViewControllerからFirstViewControllerにpopされる、
という動きになります。すなわち、「ThirdViewControllerからFirstViewControllerに一気に戻りたい」を簡単に実現できます。
また、FirstViewControllerの
backToFirst(:_)
が実行され、segue.identifier
に上で付けたfromThirdToFirst
が渡ってきます。
すなわち、backToFirst(:_)
に、画面更新など、戻り先で行うべきコードを記述することができます。蛇足
私はコードでこれらを(Unwind Segueと同程度に簡単に)実現できる方法を知りません。
もしご存知の方がいたらコメントで教えていただきたいです。
- 投稿日:2020-08-30T09:12:46+09:00
[Xcode] [Swift] 実務的Tipsシリーズ(2) Unwind Segueで2つ以上前の画面に一気に戻って画面を再更新する
今日からでもすぐに取り入れられて、
- コードをよりクリーンにできる、とか
- 工数を削減できる、とか
そんなTipsを紹介していく記事シリーズです。
「知らなかった」「気づかなかった」「忘れていた」そんな誰かの役に立てば幸いです。前提環境
- Xcode 11.3.1
- Swift 5.1.3
Unwind segue
概要・使い所
- 2つ以上前の画面に一気に戻りたい時。
- 特に、戻り先の画面にて、画面更新などの処理を行いたい時に便利。
手順
- FirstViewController > SecondViewController はpush遷移です。
- SecondViewController > ThirdViewController はmodal遷移です。
やりたいことは「ThirdViewControllerからFirstViewControllerに一気に戻りたい」です。
手順1.戻り先に関数を定義する
戻り先であるFirstViewControllerに以下のようなコードを書きます。
関数名はなんでも良いです。FirstViewController.swift@IBAction func backToFirst(_ segue: UIStoryboardSegue) { print("\(segue.identifier!)") }手順2.遷移元にUnwind Segueを定義する
Storyboardの遷移元のViewControllerの"Exit"に対して、Ctrlを押しながらオブジェクトをドラッグするとUnwind Segueを定義できます。
例えば、UIButtonから"Exit"にCtrl+ドラッグするとこんなふうに上で定義した
backToFirst
が表示されますので、それを選択するとUnwind Segueを追加できます。
Unwind Segueを追加したら任意のSegue Identifierを付けます。
実行結果
ThirdViewControllerのUIButtonをtapすると、
(1) ThirdViewControllerが閉じられ、
(2) SecondViewControllerからFirstViewControllerにpopされる、
という動きになります。すなわち、「ThirdViewControllerからFirstViewControllerに一気に戻りたい」を簡単に実現できます。
また、FirstViewControllerの
backToFirst(:_)
が実行され、segue.identifier
に上で付けたfromThirdToFirst
が渡ってきます。
すなわち、backToFirst(:_)
に、画面更新など、戻り先で行うべきコードを記述することができます。蛇足
私はコードでこれらを(Unwind Segueと同程度に簡単に)実現できる方法を知りません。
もしご存知の方がいたらコメントで教えていただきたいです。
- 投稿日:2020-08-30T09:09:52+09:00
[Swift] 実務的Tips Initialization Closureを使ってオブジェクトの初期化コードをリーダブルにする
今日からでもすぐに取り入れられて、
- コードをよりクリーンにできる、とか
- 工数を削減できる、とか
そんなTipsを紹介していく記事シリーズです。
「知らなかった」「気づかなかった」「忘れていた」そんな誰かの役に立てば幸いです。前提環境
- Xcode 11.3.1
- Swift 5.1.3
Initialization Closure
概要・使い所
プロパティが属する型が初期化されるたびに、クロージャーが呼び出され、その戻り値がプロパティのデフォルト値として割り当てられます。
特に、UIをコードで構築する場合に効果を発揮します。
プロパティの宣言部分に初期化のためのコードを集約できるため、見通しが良くなります。class ViewController: UIViewController { private let label: UILabel = { let l = UILabel() l.translatesAutoresizingMaskIntoConstraints = false l.text = "label" return l }() private let button: UIButton = { let b = UIButton() b.translatesAutoresizingMaskIntoConstraints = false b.setTitle("button", for: .normal) b.setTitleColor(.blue, for: .normal) b.sizeToFit() return b }() // 以下省略 }公式ドキュメント
- 投稿日:2020-08-30T09:09:52+09:00
[Swift] 実務的Tips: Initialization Closureを使ってオブジェクトの初期化コードをリーダブルにする
今日からでもすぐに取り入れられて、
- コードをよりクリーンにできる、とか
- 工数を削減できる、とか
そんなTipsを紹介していく記事シリーズです。
「知らなかった」「気づかなかった」「忘れていた」そんな誰かの役に立てば幸いです。前提環境
- Xcode 11.3.1
- Swift 5.1.3
Initialization Closure
概要・使い所
プロパティが属する型が初期化されるたびに、クロージャーが呼び出され、その戻り値がプロパティのデフォルト値として割り当てられます。
特に、UIをコードで構築する場合に効果を発揮します。
プロパティの宣言部分に初期化のためのコードを集約できるため、見通しが良くなります。class ViewController: UIViewController { private let label: UILabel = { let l = UILabel() l.translatesAutoresizingMaskIntoConstraints = false l.text = "label" return l }() private let button: UIButton = { let b = UIButton() b.translatesAutoresizingMaskIntoConstraints = false b.setTitle("button", for: .normal) b.setTitleColor(.blue, for: .normal) b.sizeToFit() return b }() // 以下省略 }公式ドキュメント
- 投稿日:2020-08-30T09:09:52+09:00
[Swift] 今日からすぐ使える実務的Tipsシリーズ(1) Initialization Closure
今日からでもすぐに取り入れられて、
- コードをよりクリーンにできる、とか
- 工数を削減できる、とか
そんなTipsを紹介していく記事シリーズです。
「知らなかった」「気づかなかった」「忘れていた」そんな誰かの役に立てば幸いです。前提環境
- Xcode 11.3.1
- Swift 5.1.3
Initialization Closure
概要・使い所
プロパティが属する型が初期化されるたびに、クロージャーが呼び出され、その戻り値がプロパティのデフォルト値として割り当てられます。
特に、UIをコードで構築する場合に効果を発揮します。
プロパティの宣言部分に初期化のためのコードを集約できるため、見通しが良くなります。class ViewController: UIViewController { private let label: UILabel = { let l = UILabel() l.translatesAutoresizingMaskIntoConstraints = false l.text = "label" return l }() private let button: UIButton = { let b = UIButton() b.translatesAutoresizingMaskIntoConstraints = false b.setTitle("button", for: .normal) b.setTitleColor(.blue, for: .normal) b.sizeToFit() return b }() // 以下省略 }公式ドキュメント
- 投稿日:2020-08-30T09:09:52+09:00
[Swift] 実務的Tipsシリーズ(1) Initialization Closureを使ってオブジェクトの初期化をカプセル化する
今日からでもすぐに取り入れられて、
- コードをよりクリーンにできる、とか
- 工数を削減できる、とか
そんなTipsを紹介していく記事シリーズです。
「知らなかった」「気づかなかった」「忘れていた」そんな誰かの役に立てば幸いです。前提環境
- Xcode 11.3.1
- Swift 5.1.3
Initialization Closure
概要・使い所
プロパティが属する型が初期化されるたびに、クロージャーが呼び出され、その戻り値がプロパティのデフォルト値として割り当てられます。
特に、UIをコードで構築する場合に効果を発揮します。
プロパティの宣言部分に初期化のためのコードを集約できるため、見通しが良くなります。class ViewController: UIViewController { private let label: UILabel = { let l = UILabel() l.translatesAutoresizingMaskIntoConstraints = false l.text = "label" return l }() private let button: UIButton = { let b = UIButton() b.translatesAutoresizingMaskIntoConstraints = false b.setTitle("button", for: .normal) b.setTitleColor(.blue, for: .normal) b.sizeToFit() return b }() // 以下省略 }公式ドキュメント
- 投稿日:2020-08-30T09:09:52+09:00
[Swift] 実務的Tipsシリーズ(1) Initialization Closureを使ってオブジェクトの初期化コードをリーダブルにする
今日からでもすぐに取り入れられて、
- コードをよりクリーンにできる、とか
- 工数を削減できる、とか
そんなTipsを紹介していく記事シリーズです。
「知らなかった」「気づかなかった」「忘れていた」そんな誰かの役に立てば幸いです。前提環境
- Xcode 11.3.1
- Swift 5.1.3
Initialization Closure
概要・使い所
プロパティが属する型が初期化されるたびに、クロージャーが呼び出され、その戻り値がプロパティのデフォルト値として割り当てられます。
特に、UIをコードで構築する場合に効果を発揮します。
プロパティの宣言部分に初期化のためのコードを集約できるため、見通しが良くなります。class ViewController: UIViewController { private let label: UILabel = { let l = UILabel() l.translatesAutoresizingMaskIntoConstraints = false l.text = "label" return l }() private let button: UIButton = { let b = UIButton() b.translatesAutoresizingMaskIntoConstraints = false b.setTitle("button", for: .normal) b.setTitleColor(.blue, for: .normal) b.sizeToFit() return b }() // 以下省略 }公式ドキュメント
- 投稿日:2020-08-30T07:42:40+09:00
【備忘録】【iOS】SwiftエンジニアがObject-Cを学ぶ際に最低限学んだこと
最初にさらっと読んだ記事
今さらまとめるObjective-C文法基礎
→変数やif分などの基礎文法を学ぶ。
SwiftとObjective-Cとの違い
→クラスや関数などを学ぶ。
意図せずObjective-C を読まなければいけなくなった Swift エンジニアへ贈る Objective-C読み方まとめ
→SwiftとObjective-Cの違いで大事な点だけ学ぶ。上記でさらっとObjective-Cを理解。
後は不明点が出たらググるスタンスで良いのかなと思います。以下はメモ。
.hと.mのファイルが存在する
.hファイルとは
ヘッダファイル(.h)はクラスの「目次」
→このファイルのみでクラスの全体を俯瞰できる。
→@interface〜@end
→クラスの目次(宣言)(〜終わり).mファイルとは
実装ファイル(.m)はクラスの「本体」
→@implementation〜@end
→クラス実装(〜終わり)分割メリット
※分割のメリット・・クラスを外部に公開する際、ヘッダファイルだけでもユーザはそのクラスを利用できる。
一方実装の内部は隠蔽することができる。引用先:Objective-Cの基本
importの種類
#import
クラスライブラリ(CocoaやCocoa Touchに予め用意されているクラス)をインポートするときに使う
#import "hoge.h"
自作ファイルからインポートする場合
@import hoge;
Xcode5あたりで新規作成したprojectからデフォルトでYESになっている機能だったようです。
この機能を利用するためには、BuildSettingsからEnable Modules (C and Objective-C)をYESにする必要があります。
(※古いプロジェクトでは、デフォルトがNOになっている)#import <Cocoa/Cocoa.h>などのように書いていた箇所は
@import Cocoa;で置き換えられます。引用先:frameworkのimportを簡略化する(@importについて)
おわりに
後はググりながらObjective-Cを学んでいこうと思います。
躓いた点があれば随時追記してこうと思います!
- 投稿日:2020-08-30T02:53:09+09:00
[Swift] 条件を指定して配列を分割できる`partition(by:)`の使い方
partition(by:)
とは与えた条件に一致するすべての要素が、一致しないすべての要素の後に来るように、コレクションの要素を並べ替えます。
使い方
var numbers = Array(0...10) // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] let p = numbers.partition(by: { $0 % 2 == 0 }) // 5 print(numbers) // [9, 1, 7, 3, 5, 4, 6, 2, 8, 0, 10] let odds = numbers[..<p] // [9, 1, 7, 3, 5] let evens = numbers[p...] // [4, 6, 2, 8, 0, 10]注意点
partition(by:)
は条件に一致する要素が始まるindexを返すのであって、新しい配列を返すわけではない。
- つまり
partition(by:)
は自身を変化させるmutating func
(元の配列を宣言する時はlet
ではなくvar
)- 要素を並び替えた後、条件を満たさない前半(or満たす後半)の要素の連続の中で元の順番は保証されていない
問題点 & 対応策
前述したように、
partition(by:)
は条件によって配列を2つに分割することができますが、元の配列の順番が重要で、分割後もそれを維持したいケースにこのメソッドは向いていないです(そして多分そのケースの方が多い?)対応策として
Array
を拡張するextension Array { /// 条件を満たさない要素の配列と、満たす配列をtupleにして返す func partitioned(predicate: (Element) -> Bool) -> (unsatisfied: [Element], satisfied: [Element]) { return reduce(into: ([Element](), [Element]())) { if predicate($1) { $0.1.append($1) } else { $0.0.append($1) } } } }使い方
let partitioned = numbers.partitioned(predicate: { $0 % 2 == 0}) print(partitioned.unsatisfied) // [1, 3, 5, 7, 9] print(partitioned.satisfied) // [0, 2, 4, 6, 8, 10] // 順番はそのまま!ちなみに、今回extensionに利用した
reduce(into:_:)
については、こちらに素晴らしい記事があります。また、パフォーマンスについては
reduce(into:)
ではなく素直にfor in
を使って回す方法もありますが、リリースビルドにおいては問題無いようです。
(あっても自分はreduceで書きたいが?)最後に
当然ですが、分割した配列内の要素の順番を気にしないのであれば、パフォーマンスの観点からSwift標準の
partition(by:)
を使うべきですね?