- 投稿日:2021-01-25T23:34:15+09:00
エラー「Application tried to present modally a view controller」の時
環境
・Xcode: 12.3
・Swift5以下のようなエラーログがでてクラッシュしてしまった時
libc++abi.dylib: terminating with uncaught exception of type NSException *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Application tried to present modally a view controller <ProjectName.ViewController: 0x158331780> .that has a parent view controller <UINavigationController: 0x159877a00>.' terminating with uncaught exception of type NSExceptionこんなパターンがあるよ
上記のようにViewControllerに対してNavigationControllerがいる場合に、
ViewControllerを表示しようとしてしまっている可能性let storyboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil) if let navigationVc = storyboard.instantiateInitialViewController() as? UINavigationController, let viewController = navigationVc.topViewController as? ViewController { // これは× // present(viewController, animated: true, completion: nil) // これは○ present(navigationVc, animated: true, completion: nil) }これに限らないかと思いますが、誰かの役に立てば。
- 投稿日:2021-01-25T18:43:13+09:00
inputViewを使ってカスタムキーボードをつくろう!(Swift)
はじめに
UITextField
やUITextView
で入力する際はキーボードの入力タイプが色々用意されていますが実はこのキーボード色々カスタムができます!たぶん実用的なのは
UIPickerView
とかUIDatePicker
を表示したりとかです(なんか昔半角カナ用キーボードを作った記憶があるけどあんまよくなかった)。これからはもう
UIKit
を使うことも少なくなっていくのかもしれませんが備忘録として。。。*注:「つくろう」って書きましたがとくにカスタムキーボードを薦めるものではありません。
UITextFieldをカスタマイズ
試しに
UITextField
のキーボードをカスタムしてみます。やり方は簡単でinputView
に任意のView
を設定するだけで OK です// こんなのとか let keyboard = CustomKeyboardView(frame: .init(origin: .zero, size: .init(width: 284, height: 284))) keyboard.delegate = self textField.inputView = keyboard // こんなのとか let picker = UIPickerView() picker.delegate = self picker.dataSource = self textField.inputView = pickerあとはデリゲートとかで入力文字を受け取って
UITextField
に表示するだけです。こんな感じ。
カスタム View ピッカー カスタム View はこんな感じです。
xib
protocol CustomKeyboardViewDelegate: AnyObject { func customKeyboardView(_ customKeyboardView: CustomKeyboardView, didSelectKey key: String) } final class CustomKeyboardView: UIView { weak var delegate: CustomKeyboardViewDelegate? override init(frame: CGRect) { super.init(frame: frame) loadNib() } required init(coder aDecoder: NSCoder) { super.init(coder: aDecoder)! loadNib() } private func loadNib() { let view = Bundle.main.loadNibNamed("CustomKeyboardView", owner: self, options: nil)?.first as! UIView view.frame = bounds view.translatesAutoresizingMaskIntoConstraints = true view.autoresizingMask = [.flexibleWidth, .flexibleHeight] addSubview(view) } @IBAction private func selectKey(_ sender: UIButton) { delegate?.customKeyboardView(self, didSelectKey: sender.titleLabel!.text!) } }キーボードの高さはわりと自由みたいです
サクッとカスタムキーボードが表示できました!ただカスタムキーボードを入力制限目的で利用するにはいくつか問題があるのでオススメしませんコピペとか外部キーボード接続とか。。。
コピペに関しては
UITextField
のカスタムクラスをつくってcanPerformAction
ごにょごにょすればいける気がしますが外部キーボードに関しては防げないと思われます(そもそもあんまりUITextField
で入力制限なんてしない方がいいと思います)。UIControlをカスタマイズ
とくに
UITextField
のinputView
に設定する!でも問題ないのですがUITextField
を使うとなるとレイアウトの自由度が低いのでせっかくなんでカスタムクラスをつくった方が自由度が高いのでおすすめです下記のように
UIControl
を継承したクラスをつくってcanBecomeFirstResponder
とinputView
を設定してやるとカスタムキーボードが表示できます。UIView
じゃないのはaddAction
がしたかったからです。フォーカス時がわかりにくいので枠線の色を変更しています。protocol PickerInputControlDelegate: AnyObject { func pickerInputControl(_ pickerInputControl: PickerInputControl, didSelectValue value: String) } final class PickerInputControl: UIControl { var items = [String]() weak var delegate: PickerInputControlDelegate? override init(frame: CGRect) { super.init(frame: frame) commonInit() } required init(coder aDecoder: NSCoder) { super.init(coder: aDecoder)! commonInit() } private func commonInit() { layer.cornerRadius = 8.0 layer.borderWidth = 1.0 layer.borderColor = UIColor.systemGray.cgColor addAction(.init(handler: { [weak self] _ in self?.isSelected.toggle() if self?.isSelected == true { self?.becomeFirstResponder() } else { self?.resignFirstResponder() } }), for: .touchUpInside) } override var inputView: UIView? { let picker = UIPickerView() picker.delegate = self picker.dataSource = self return picker } override var canBecomeFirstResponder: Bool { return true } override var isSelected: Bool { didSet { layer.borderColor = isSelected ? UIColor.systemBlue.cgColor : UIColor.systemGray.cgColor } } @discardableResult override func becomeFirstResponder() -> Bool { let value = super.becomeFirstResponder() isSelected = isFirstResponder return value } @discardableResult override func resignFirstResponder() -> Bool { let value = super.resignFirstResponder() isSelected = isFirstResponder return value } } extension PickerInputControl: UIPickerViewDelegate { func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { delegate?.pickerInputControl(self, didSelectValue: items[row]) } } extension PickerInputControl: UIPickerViewDataSource { func numberOfComponents(in pickerView: UIPickerView) -> Int { return 1 } func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { return items.count } func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { return items[row] } }こんな感じで
UIControl
なので中にView
置き放題です。
UIPickerViewDelegate
,UIPickerViewDataSource
,items
は外に出した方が汎用性が高いかもstatic cellと組み合わせる
おまけで
UITableViewController
の static cell と組み合わせるとフォーカス時に自動スクロールしてくれるので複数の入力項目がある場合はおすすめですこんな感じ。
おわりに
カスタムキーボードを作る際はタップ領域をちゃんと確保してあげると素敵です。ちょっと古い記事ですが下記の記事はキーボードのタップ領域とか詳しく調べていておもしろいです。
わりとお世話になった手法だけどこれからはあんまり使わなくなるのかな
UIKit
。。。調べてて気になったけどこいつ知らねえ
参考
- 投稿日:2021-01-25T17:00:23+09:00
【Swift】RxSwiftが難しいのでサンプルコードを作った
RxSwiftが難しい
「RxSwiftについていろんな記事を読みまくったけど、結局わからん」
その気持ちめっちゃわかります。
難しいですよね〜特に独学だとめちゃめちゃ辛い。
とりあえずRxSwiftはデータバインディングができて、
それを利用してMVVMなんかに使われることが多いです。RxCocoaと併用しよう
RxSwiftはRxCocoaとよく併用されます。
ViewModel
ModelTypeでInputとOutputのプロトコルを返すことで、
viewModel.outputs.output、
viewModel.inputs.input
のようにそれがoutputsに登録したものなのか
inputsに登録したものなのかが明確になる。
extensionで準拠させよう。inputsはViewControllerから受け取るイベントで、
受け取りなのでObserverを使う。outputsはinputsでViewControllerから受け取ったイベントによって
処理をして、その結果をViewControllerに返すのでObservableを使う。Observerは受信者(監視)、Observableは発信者であるので
それぞれoutputsとinputsで使い分けている。output
let _output = BehaviorRelay<Bool>(value: false) self.output = _output.asObservable() //Observableを代入し変数outputを初期化している。input
self.input = AnyObserver<Bool>() { bool in //Observerなので、ViewControllerからinputに流れてきた //イベントをここで受け取る。 boolはそのイベント。 _output.accept(bool) //Observable(発信者)にイベントを発信させるには //accept()を使う。 }こんな感じで、ViewModelはVCからイベントをinputで受け取り、
outputで処理結果を返す感じに使う。
VCではinputでViewModelにイベントを送り、
outputを監視して処理結果に応じたUIの更新を行う。ViewController
Instantiateを使うとbundleNameとか使わなくても、
storyboardを呼び出せる。(別々のstoryboardを使っていてもこれで一発なので超便利)
dependencyやinjectは依存性的な感じ。ここは自分で調べてください。var viewModel: RxSwiftViewModelType! //viewModelをModelTypeで型宣言してあげることで、 //inputs, outputs変数(プロトコル)を通さなければならなくなり、 //イベントがinputsなのかoutputsなのかはっきりする。(重要)input
let _ = viewModel.inputs.input.onNext(true) //ViewModelにイベントを送りたいときは、onNextメソッドを使う。 //画面更新が入る時などoutput
let _ = viewModel.outputs.output .subscribe(onNext: { [weak self] bool in //viewModelからのイベント結果を監視し、イベントがきたら発火する //weak selfはメモリリーク対策 guard let self = self else {return} //guard let もメモリリーク対策 //イベント結果がきた時にしたい処理を書く }).disposed(by: disposedBag) //disposedBagを入れることで、購読の重複を防ぐ。 //(詳しくは調べてください)遷移
instantiateライブラリを使っているので、遷移の仕方は少し特殊
この場合はnavigationControllerを使ったパターン。まとめ
RxSwift、難しいですよね。
さらっとやったらあとは実務でゆっくり身に付けるのが一番効率が良さそうです。ちなみに、RxSwiftの参考書なら RxSwift研究読本がおすすめです。
- 投稿日:2021-01-25T16:43:39+09:00
[Swift]数字の配列から合計(sum)と平均(average)を計算
swiftで数字の配列
[1, 2, 3, 4, 5]など
から合計と平均を出す機会が何度かあったのですが、毎回調べて実装していたので、まとめてみました。Array.reduceを使う
数字の配列の合計を計算する場合、愚直にforEachを使う方法もありますが、Array.reduceを使うと簡潔に書けます。
[1, 2, 3, 4].reduce(0, +) =>結果は10第1引数は初期値(今回は0)です。
第2引数はオペレーターです。+-*/
の4つが使えます。参考
https://developer.apple.com/documentation/swift/array/2298686-reduceextensionを使い、合計と平均を簡単に計算する
Int配列を例に、合計と平均を出すmethodを定義します。
Arrayのベースextension Collection where Element == Int { func sum() -> Int { return reduce(0, +) } func average() -> Double { let value = sum() return Double(value) / Double(count) } }ソース
Intだけでなく、Float, Doubleの配列についてもextensionにしたソースを以下に置きました。
https://gist.github.com/usk2000/230d46869c9eba401978a8865d2f66fa終わりに
今回はよく使う合計と平均について書いてみましたが、もっと数学的なものも書いてみたいですね。
参考
https://qiita.com/motokiee/items/cf83b22cb34921580a52
https://developer.apple.com/documentation/swift/array/2298686-reduce
- 投稿日:2021-01-25T12:33:33+09:00
【Swift】AutoLayoutでUI部品のサイズを比率で制約したい
はじめに
現在、Swiftの学習を初めて一ヶ月ほどになり、ちょっとしたアプリを作ってみたりしています。
そこでデバイスサイズにより、レイアウトが崩れてしまうという壁にあたりました。各デバイスの画面サイズに対応できるようAutoLayoutに挑戦しましたが全然わかりませんでした。
試行錯誤していくうちに、自分の実現したかったUI部品のサイズが画面の高さの比率で変化するやり方をなんとなく理解したのでここに記録します。(間違えていたり、誤認識している場合ご指摘お願いいたします。)開発環境
- macOS Catalina version10.15.7
- Xcode version12.2
- Swift5
AutoLayoutについて
こちらの記事が大変わかりやすく参考になりました。
怖くない!AutoLayout 〜多画面対応 with Storyboard〜AutoLayoutではいろいろ設定ができるため、自分のような初学者には難しいので今回必要なもののみに焦点を当てます。
今回の重要なもの
こちらの二つ
- Spacing to nearest neighbor
- Equal Heights
Spacing to nearest neighbor
は直訳すると「最近傍への間隔」ということで近くにある部品との距離を指定することのできるものです。下記画像の箇所で指定します。
各項目の詳細については先ほどの記事を参考にしてください。二つ目に重要なものもはこの画像に写っている、
Equal Heights
です。
Equal Heights
は2つ以上のUI部品を選択した状態のときに使用することができる設定で、選択した複数部品の高さを同じ値に揃えることができます。これを用いて、画面高さとUI部品をEqualHeightsで設定してあげれば良い。
でも、画面高さと同じだと全然使えないじゃん!と自分は思いました。安心してください。今回で言えば、画面高さに対して
比率でサイズを指定することができます
。
これを使うことで、画面高さに対して常に50%の大きさにしたい!など実現できます。
※同様にEqualWidthsを使うことで、横幅も比率指定できます!やってみる
まずは適当にUI部品(部品Aとします)を設置します。この部品AをSafeArea高さに対して25%の高さにしたいと思います。
次に、
Spacing to nearest neighbor
を使って、画面上部と左右に対して距離を0とします。
下記画像のように指定したら、Add 3 Constraints
を押すことで適用されます。そうすると、このようにSafeAreaの上部と左右の距離が0、つまり部品Aの幅=デバイスの幅となります。左の階層部分をみると、
Constraints
というものが追加されています。
これがAutoLayoutで設定した制約です。ここをクリックして設定値を変えたりできます。次に、
Equal Heights
を用いてSafeAreaと部品Aの高さを同じになるよう設定します。
やり方は、左の階層部分のSafe Area
と部品A
を選択(Macならcommandを押しながらクリック)し、Equal Heights
を設定します。そうするとこのようになります。部品AがSafeAreaと同じ高さになったため、このように画面いっぱいに表示されます。
また、階層部分に部品A(25%).height = Safe Area.height
という制約が追加されました。最後にやることは、比率の設定です。今回はSafeAreaに対して25%の高さにします。先ほど追加された
部品A(25%).height = Safe Area.height
という制約を選択します。そうすると、下記画像のようにEqual Heights Constraintが表示されるので
Multiplier
の値をみると1となっています。これは、部品AがSafeAreaの高さに対して1.0(100%)の高さ、つまり同じ高さということになっている。この値を0.25(25%)と設定すればOK。無事、SafeAreaに対して25%の高さに設定することができました。
制約部分を見てみると、部品A(25%).height = 0.25 × Safe Area.height
としっかり設定ができていることが確認できます。高さの比率を変えたい場合は、先ほどと同様の手順で値を変えることで変更できます。同様の手順で、他の部品も設置してみましょう。
今回は25%/45%/30%と分けて設置してみました。他のデバイスサイズでも確認してみます。こんな感じで、画面サイズが変わっても指定の比率通りにサイズが変更されています。
まとめ
今回は、AutoLayoutを用いて、UI部品のサイズを比率で制約しました。
引き続きいろいろ触ってみて、AutoLayoutを意のままに操れるよう頑張ります。以上。
- 投稿日:2021-01-25T09:46:39+09:00
Realm Studioを使う途中で【エラー対処】Opening Realm files of format version 20 is not supported by this version of Realm
RealmStudioの使い方
RealmStudioを起動し Open Realm file からRealmファイルを開くことができます。
RealmファイルはAppDataのDocumentフォルダーの中にdefault.realmファイルが存在すると思います。
→参考記事 AppDataの参考記事しかし、エラー発生?
Opening Realm files of format version 20 is not supported by this version of Realm
解決方法
Githubのissue
Realm Studio リリースページ
ここから最新版をインストールすることでRealmファイルを開くことができます。