20210430のSwiftに関する記事は18件です。

【備忘録】配列の中の空文字を取り除く(.filter)

はじめに .filterを使って配列の中の空文字を取り除く処理について備忘録として残します (初心者向け) やりたいこと ["a", "b", "c", "", "e", "", "g"] この配列から空文字を取り除いて、文字だけの配列にする 初心者パターン for文で書く 私は元々はこう書いていました。(初心者) beginner let list1 = ["a", "b", "c", "", "e", "", "g"] var list2 = [String]() for hoge in list1 { if hoge != "" { list2.append(hoge) } else { continue } } print(list2) //["a", "b", "c", "e", "g"] 上級者パターン .filterをつかう advanced let list1 = ["a", "b", "c", "", "e", "", "g"] let list2 = list1.filter{!$0.isEmpty} print(list2) //["a", "b", "c", "e", "g"] 1行で書けた 解説(初心者向け) 「$0」には、配列の中身が1つずつ入ってくる。 「isEmpty」は空かどうかをみてくれる。 (空の時はtrue/そうでない時はfalseを返してくれる) ↓ .filter{$0.isEmpty} = 空だけ取り出される 「!」をつけると意味が反対になる(~ではない) ↓ .filter{!$0.isEmpty} = 空でない物を取り出す おわり 初めは慣れないですが、慣れればとても便利です。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

失敗のあるイニシャライザ

失敗のあるイニシャライザとは ???? 失敗のあるイニシャライザとは、引数が誤っていたり、データの形式が間違っていたりした場合、 インスタンスの代わりにnilを返すことによって以上を知らせる機能のことです。 通常のinitに?をつけて記述します。 例えば、構造体Humanで、nameプロパティに長さが5以上の引数が入力された場合、イニシャライザを失敗させてみましょう。 失敗のあるイニシャライザの定義 init.playground struct Human { var name: String! init?(_ name: String) { guard name.count < 5 else { print("イニシャライズ 失敗") return nil } self.name = name print("イニシャライズ 成功") } } initのあとに?がついているのがわかります。 そのあと、「nameの長さが5以上」のときに、return nilしていることがわかり、これこそがイニシャライザの失敗が実装されている部分です。 逆に、「nameの長さが5未満」のときは、self.name = nameで、適切にプロパティに初期値が格納されます。 インスタンス化 では実際にインスタンス化してみましょう let human1 = Human("hogehoge") print(human) // >>イニシャライズ 失敗 // >>nil この例だと引数nameに5文字以上の値が入力されるため、guard文に引っかかり失敗します。 let human1 = Human("ken") print(human) // >>イニシャライズ 成功 // >>Optional(__lldb_expr_21.Humans(name: Optional("ken"))) この例だと引数nameに5文字未満の値が入力されるため、適切に初期化されます。これが失敗のあるイニシャライザの機能でした! 余談:詳細swiftって本読みやすくて面白いですよ
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SwiftUIでLazyなGridとStackの使い方を学ぶよ

おはよう 今日はLazyVGrid,LazyVStack,LazyHGridLazyHStackの使い方を勉強するよ。 Lazyってなにかざくっと勉強 Stackは画面を開いたときにその中の要素をすべて読み込み・描写します。 一方Lazy〜は必要に応じてViewを読み込み・描写をするという違いがあります。 例えば、表示したいViewのサイズが大きかったり、個数が多い場合、Stackで画面を作ってしまうと、パフォーマンスが低下する危険性があります。 Lazy〜を使うことで、必要に応じて読み込みと描写をさせ、処理を低下させずに画面描写を行うことができるようです。 Lazy〜はそれぞれiOS14以上から使用できる比較的新しい構造体です。 公式:Creating Performant Scrollable Stacks LazyVGridとLazyVStackの使い方 公式:LazyVGrid 公式:LazyVStack 【サンプルPGM】 LazyVGrid.swift import SwiftUI //2. GridItem let columns: [GridItem]=[GridItem(),GridItem()] struct LazyView: View { var body: some View { NavigationView { //3.ScrollView ScrollView(.vertical) { /*---LazyVGridの表示---*/ // 1.LazyVGrid LazyVGrid(columns: columns, pinnedViews: [.sectionHeaders]) { // header, footerで呼び出しているLazyPinnedViewは下のPGMで定義しています。 Section(header: LazyPinnedView(title: "GridHeader", color: Color.blue), footer: LazyPinnedView(title: "GridFooter", color: Color.green)) { ForEach(0..<20) { i in Text("Grid: \(i)") .frame(width: 100, height: 50) .background(Color(red: Double(i * 10) / 255, green: 1 ,blue: Double(i * 30) / 255 )) } } } /*---LazyVStackの表示---*/ // 5.LazyVStack LazyVStack(spacing: 3, pinnedViews: [.sectionHeaders]) { Section(header: LazyPinnedView(title: "ListHeader", color: Color.yellow), footer: LazyPinnedView(title: "ListFooter", color: Color.red)) { ForEach(0..<15) { i in HStack { Spacer() Text("List: \(i)") Spacer() }.padding(.all, 30) .background(Color(red: 1, green: Double(i * 30) / 255 ,blue: Double(i * 10) / 255)) } } } } .navigationTitle("Navigation Header") } } } struct LazyView_Previews: PreviewProvider { static var previews: some View { LazyView() } } LazyPinnedView.swift import SwiftUI struct LazyPinnedView: View { private var title: String private var color: Color init(title: String, color : Color){ self.title = title self.color = color } var body: some View { HStack { Spacer() Text("\(title)") Spacer() }.padding(.all, 10).background(color) } } struct LLazyPinnedView_Previews: PreviewProvider { static var previews: some View { LazyPinnedView(title:"previewHeader", color :Color.blue) } } 【挙動】 1. LazyVGrid LazyVGridは下記のような形で使用するよ。 LazyVGrid (columns: XXXX, PinnedScrollableViews : YYYY) columns:XXXX GridItem型に則って、1行のGridの個数や、1要素のサイズ・行間を指定する。 今回は1行のカラム数=GridItem2個で定義しているね。 これを3つにするとこのように変わります。 下記のような形で定義もできます。 let columns: [GridItem] = Array(repeating: .init(.flexible()), count: 4) ///Array(repeating: X-リピートしたい文字-X, count: Y-回数-Y) PinnedScrollableViews:YYYY [sectionHeaders][sectionFooters]のどちらかを指定。 リストをスクロールした際に固定される要素を決めているよ。 最初のサンプルはsectionHeadersを使用しています。 sectionFootersを選択した場合の挙動は下記のとおりです。 【挙動】 2.GridItem 公式:GridItem 1個1個のGridItemを定義するのに使うよ。 すべてのGridItemは個別にサイズや間隔を設定することができます。 サイズの設定の仕方としては、fixed, flexible,adaptiveの3つのやり方があります。 fixed 固定値としてサイズを設定します。 例えば下記のように、1行2カラムをそれぞれ固定値と間隔・寄せを設定した場合 let columns: [GridItem] = [ GridItem(.fixed(150), spacing: 16, alignment: .top), GridItem(.fixed(200), spacing: 20, alignment: .leading) ] 1行3カラムで、Frameサイズより小さくGridを設定した場合は、画像のように要素同士が重なって表示されるよ。 let columns: [GridItem] = [ GridItem(.fixed(50),spacing: 16), GridItem(.fixed(70),spacing: 16), GridItem(.fixed(100),spacing: 16) ] 公式:fixed(_:) flexible 空いているスペースに合わせて拡張したり縮小したりしてカラムサイズが変わる設定の仕方で、最小値と最大値を設定することができます。 デフォルト値は、minimum(最小)が10、maximum(最大)がinfinityのようです 1行2アイテムの場合 private var columns: [GridItem] = [ GridItem(.flexible(maximum: 150)), GridItem(.flexible()) ] 1行3アイテムの場合 private var columns: [GridItem] = [ GridItem(.flexible(minimum: 70, maximum: 150)), GridItem(.flexible(minimum: 70, maximum: 150)), GridItem(.flexible()) ] 公式:flexible(minimum:maximum:) adaptive flexibleは1行のなかで柔軟にアイテムを配置していましたが、adaptiveはその行にまだItemを入れるスペースがある場合は、行を超えて配置がされます。 なので、例えば下記のように設定しても1行3アイテムで詰めて表示がされます。 private var columns: [GridItem] = [ GridItem(.adaptive(minimum: 100, maximum: 100)), GridItem(.adaptive(minimum: 100)) ] 公式:adaptive(minimum:maximum:) 3.ScrollView ScrollView(.vertical) ・・ 縦方向のスクロール ScrollView(.horizontal) ・・ 横方向のスクロール LazyVGrid, LazyVStack, LazyHStackは必要なアイテムだけ読み込むので、 ScrollViewの中に配置しないと、すべての要素が画面上に表示されなくなってしまうよ。 4.LazyVStack LazyVStackは下記のように使うよ。 alignmentやspacingなどの要素はすべて任意なので、不要であればなしでOKだよ。 pinnedViewsはLazyVGridと同じ使い方をするよ。headerとfooterでの固定され方の違いは前のgifをみてね。 LazyVStack(alignment: XXXX, spacing:XXXX, pinnedViews: XXXX,content: XXXX) { LazyHGrid, LazyHStackの使い方 公式:LazyHGrid 公式:LazyHStack 基本的な使い方はLazyVGrid, LazyVStackと同じです。 今回は比較のため、最初のPGMをLazyH~用に最低限に書き換えているので、 すこしレイアウトがおかしいですが、ご了承ください。 LazyView.swift let rows: [GridItem] = [GridItem(),GridItem()] struct LazyView: View { var body: some View { NavigationView { // 変更点1 ScrollView(.horizontal) { VStack { // 変更点2 LazyHGrid(rows: rows, alignment: .top, pinnedViews: [.sectionHeaders]) { Section(header: LazyPinnedView(title: "GridHeader", color: Color.blue), footer: LazyPinnedView(title: "GridFooter", color: Color.green)) { ForEach(0..<20) { i in Text("Grid: \(i)") .frame(width: 100, height: 50) .background(Color(red: Double(i * 10) / 255, green: 1 ,blue: Double(i * 30) / 255 )) } } } //変更点3 LazyHStack(spacing: 3, pinnedViews: [.sectionHeaders]) { Section(header: LazyPinnedView(title: "ListHeader", color: Color.yellow), footer: LazyPinnedView(title: "ListFooter", color: Color.red)) { ForEach(0..<10) { i in HStack { Spacer() Text("List: \(i)") Spacer() }.padding(.all, 30) .background(Color(red: 1, green: Double(i * 30) / 255 ,blue: Double(i * 10) / 255)) } } } } } .navigationTitle("Navigation Header") } } } Header固定 【挙動】 Footer固定 【挙動】 変更点1 ScrollView LazyV〜の場合は縦方向のスクロールverticalを設定していましたが、 LazyH〜の場合は横方向のスクロールhorizontalに変更。 変更点2 LazyHGrid LazyVGridのときはcolumnsで1行あたりのカラム数を設定していましたが、 LazyHGridのときは、行数を設定しています。 どちらもGridItemを必要な個数配置して、設定しています。 変更点3 LazyHStack 単にLazyVStack→LazyHStackで置き換えただけです。同様な設定で使用できます。 まとめ 今回はLazy系の機能を使ってみて、動きや作り方を学んだよ。 headerが固定されます!とか、Gridの個数を設定できます!とか、実際動かしてみると、 理解が深まった気がしました。 理解に誤りがあれば、ご指摘いただけると幸いです
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Swift]継承が必要な処理ごとにextensionでコードを分ける

extensionで拡張する 一つのclassでいくつも継承すると、コードを読む時にどこで拡張機能を使用しているのか分かりにくい。 そこで、継承が必要な処理ごとにextensionでclassを拡張し、処理を分けることで どの処理が準拠しているのかを分かりやすくする class HogeViewController: UIViewController { ~~~ } extension HogeViewController: UITextFieldDelegate { func textFieldShouldReturn(_ textField: UITextField) -> Bool { textField.resignFirstResponder() return true } } textFieldShouldReturnはUITextFieldDelegateを継承しないと使えないので 今回はextensionでHogeViewControllerを拡張し、そこで継承が必要な処理をまとめている もちろん、class宣言時にまとめて class HogeViewController: UIViewController, UITextFieldDelegate { ~~~~~ } って書いても問題はない でも、継承するもので処理を分けた方がコードを読む時に分かりやすいのでextensionで拡張する方がいい。 (例の状態だとピンとこないかもしれないが、継承先が5個以上とかになった場合に、どの処理で何から継承したメソッドを使っているのかが分かりづらくなる。) また、拡張したHogeViewControllerの方でUITextFieldDelegateを継承していれば、 元のclassの方のHogeViewControllerでもUITextFieldDelegateに準拠したメソッドを使うことができる。 class HogeViewController: UIViewController { func textFieldShouldReturn(_ textField: UITextField) -> Bool { textField.resignFirstResponder() return true } } extension HogeViewController: UITextFieldDelegate { } その為、本来継承が必要な処理をextensionではなく元のclassの方で使用していても、問題なく動作してしまう。そうすると逆に可読性が下がるかもしれないので注意が必要
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

クラスのメモリ管理

Swiftはクラスのメモリ管理にARCという方式を採用しています。 ARCでは、インスタンスが初めて生成された時に参照カウントが1になり、以降そのインスタンスへの参照が増えると参照カウントがインクリメントされ、減るとデクリメントされます。そして、参照カウントが0になると、インスタンスも自動的に破棄されます。 デイニシャライザ class クラス名 { deinit { クリーンアップなどの終了処理 } } ARCによってインスタンスが破棄されるタイミングで、クラスのデイニシャライザが実行されます。 初期化は一番最初に呼ばれるイメージですが、デイニシャライザはメモリがすべて開放された後の一番最後に呼ばれるというイメージです。なので、デイニシャライザの中にはクリーンアップなどの終了処理を実装します。 値の比較と参照の比較 class SomeClass: Equatable { static func ==(lhs: SomeClass, rhs: SomeClass) -> Bool { return true } } let a = SomeClass() let b = SomeClass() let c = a // 値は同じ a == b // true // 参照先は別 a === b // false // aとcの参照先は同じ a === c // true 参照先の値の比較と、参照先の比較は別になります。 値比較は==, 参照先比較は===を使用します。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

2段階初期化

クラスのイニシャライザには、型の整合性を保った初期化を実現するために、3つのルールがあります。 ・指定イニシャライザはスーパークラスの指定イニシャライザを呼ぶ ・コンビニエンスイニシャライザは同一クラスのイニシャライザを呼ぶ ・コンビニエンスイニシャライザは最終的に指定イニシャライザを呼ぶ スーパークラスとサブクラスのプロパティの初期化順序を守るため、指定イニシャライザによるクラスの初期化は次の2段階に分けて行われます。 1、クラス内で新たに定義されたすべてのストアドプロパティを初期化し、スーパークラスの指定イニシャライザを実行する。スーパークラスでも同様の初期化を行い、大元のクラスまで遡る。 2、ストアドプロパティ以外の初期化を行う class User { let id: Int init(id: Int) { self.id = id } func printProfile() { print("id: \(id)") } } class RegisteredUser: User { let name: String init(id: Int, name: String) { // 第一段階 self.name = name super.init(id: id) // 第二段階 self.printProfile() } }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

finalキーワード

オーバーライド可能な要素の前にfinalキーワードを記述することで、その要素がサブクラスでオーバーライドされることを禁止できます。 class SuperClass { func overridableMethod() {} final func finalMethod() {} } class SubClass: SuperClass { override func overridableMethod() {} // オーバーライド不可能なためエラー override func finalMethod() {} } クラス自体にfinalキーワードを付与することで、そのクラスを継承したクラスを定義することを禁止することができます。 class Class1 {} class class1: Class1 {} final class FinalClass {} // 継承不可能なためコンパイルエラー class class2: FinalClass {}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

オーバーライド

class クラス名: スーパークラス名 { override func メソッド名(引数) -> 戻り値の型 { メソッド呼び出し時に実行される文 } override var プロパティ名: 型名 { get { return文によって値を返す処理 superキーワードでスーパークラスの実装を利用できる } set { 値を更新する処理 superキーワードでスーパークラスの実装を利用できる } } } オーバーライドとは、スーパークラスで定義されているプロパティやメソッドなどの要素は、サブクラスで再定義することができること。 class User { let id: Int var message: String { return "Hello." } init(id: Int) { self.id = id } func printProfile() { print("id: \(id)") print("message: \(message)") } } class RegisteredUser: User { let name: String override var message: String { return "Hello, my name is \(name)" } init(id: Int, name: String) { self.name = name super.init(id: id) } override func printProfile() { super.printProfile() print("name: \(name)") } } let user = User(id: 1) user.printProfile() print("---") let registeredUser = RegisteredUser(id: 2, name: "Yusei Nishiyama") registeredUser.printProfile() // 出力 id: 1 message: Hello. --- id: 2 message: Hello, my name is Yusei Nishiyama name: Yusei Nishiyama
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

iPhoneアプリ開発ソフト『Xcode』の基本

「Xcode」の基本 筆者について こんにちは。このページを開いてくださったことを感謝します。私は4月に大学に入学して、ついこの間プログラミングを始めましたというだだの初心者です。こんな調子乗って記事を書いてしまっていいのかなという感じですが頑張って書かせていただきます。最後まで見てくださると大変嬉しいです。 Xcodeとはなんなのか まずXcodeというものがなんなのかということから説明したいと思います。 XcodeとはIOSアプリを開発するための環境です。このXcodeはMacOSでしか動かす事ができません。 Xcodeを使用してIOSアプリ開発をしたい場合はMacを購入することをお勧めします。最新バージョンXcode12.5の動作環境はmacOS Big Sur11以降となっているのであまり古いMacを買ってしまうと動作しない場合がありますので、新規購入される際は気をつけてください。その他動作環境についてはもっと詳しく書かれているサイトがありますのでそちらを参照してください。 Xcodeでのアプリ開発 Xcodeにはアプリ開発でとても有用なツールがあり、それらはアプリ開発において強力なサポートとなります。 その中でも主要なツールについて解説します。 1.テキストエディタ テキストエディタにはソースコードを書いたり、メモを書いたりする事ができます。Xcodeはコードの自動入力、プログラム構文の強調、コードエラーを自動検出する機能、修正候補をあげてくれる機能などたくさんの機能が備わっています。 2.デバッガ デバッガを使うことによりプログラムの修正支援を受けたり、プログラムの動作に異常がないかなどをプログラムの実行途中に確認する事ができます。 3.コンパイラ コンパイラとはプログラム言語を機械語に直してくれる機能です。 4.インターフェースビルダー これを利用することでドラッグ&ドロップでボタンなどの部品を配置する事ができます 5.IOSシュミレーター 実際のiPhoneのようなシュミレーターが立ち上がり、アプリの動作がちゃんと行われるかの確認をする事ができます Xcodeの画面構成 Xcodeの画面構成は上の写真のようになっています。番号順に説明していこうと思います。 ①ナビゲーターエリア 上の画像では赤色のところとなっています ナビゲーターエリアは編集するファイルを指定するエリアです ナビゲーターエリアは3つの領域から構成されています。 上から順番にナビゲーターバー、本体領域、フィルターバーで構成されています ナビゲーターバー ナビゲーターバーは本体領域に表示される項目を変える事ができます 本体領域 ナビゲーターバーの選択によって表示される項目が変わります 本体領域には8種類の項目が表示されるようになっています 本体領域に表示される8種類のナビゲーター ①プロジェクトナビゲーター ここのナビゲーターはよく使うナビゲーターです プロジェクトのファイルを見ること、操作することができます ここでファイルを選択するとエディタエリアにファイルの内容が表示されます ②シンボルナビゲーター プロジェクトで定義されている関数やメゾットを表示してくれます ここを選択するとシンボルに当てはまる箇所がエディタエリアに表示されるようになっています ③検索ナビゲーター 検索ができるナビゲーターです 検索方法などが細かく指定する事ができます ④問題ナビゲーター 警告やエラーが表示されます ⑤テストナビゲーター XCTestのテストケースが表示されます。テストをすることもできます ⑥デバックナビゲーター Xcodeから実行したアプリのリソースの利用状況、各スレッドのメゾットや関数の呼び出し階層を表示します ⑦ブレークポイントナビゲーター デバックの実行時に処理を停止する位置のことをブレークポイントといい、ここではその条件などを設定できます ⑧レポートナビゲーター ビルド、実行、テストなどの各種履歴を表示します もっと詳しい内容がエディタ領域に表示されます フィルターバー 本体領域の表示内容を絞り込む事ができます ②エディタエリア メインで作業をする場所です 選択したファイルによってのエディタが表示されます ストーリーボードというところではレイアウトを見ながらアプリの見た目を作る事ができます ストーリーボードはよく使うと思うので覚えておいた方がいいでしょう ③ツールバー シュミレーターを起動したりする事ができます ④ユーティリティーエリア エディターエリアで選択した項目についての情報が表示されたり、編集する事ができます。 エディタエリアにドラッグ&ドロップで部品を追加したりできる項目もあります ↑ここもよく使うと思います 終わりに 今回初めてQiitaを書きました 拙い文章でほんとに読みにくかったと思うんですけど最後まで見てくださってありがとうございました また書くと思いますのでその時もよろしくお願いします 参考文献
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

XCode12.5 のインストールが終わらない

状況 XCode12.5をインストールするため、まずCatalinaをBigSurにアップデート これはできた 次にAppStoreからXCodeを更新 1時間半以上たっても終わらない 解決法 ひたすら待つ 一晩くらい 待ったのちに再起動 それでも終わらない場合 自分の場合は一晩(9時間以上)たっても終わってなかった いったんMac再起動 再起動したらXCodeが12.5でした 備考 一時間半待った時に再起動したのですが、その時はダメでした(インストールできてなかった) 思い切って一晩放置しとくのがよさそう
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

メソッド内のストアドプロパティの変更にはmutatingキーワードが必要

struct SomeStruct { var id: Int init(id: Int) { self.id = id } mutating func someMethod() { id = 4 } } var a = SomeStruct(id: 1) a.someMethod() a.id // 4 構造体のストアドプロパティの変更には再代入を必要とするため、ストアドプロパティの変更を含むメソッドにはmutatingキーワードが必要。 struct SomeStruct { var id: Int init(id: Int) { self.id = id } func someMethod() { id = 4 // mutiaingがついていないのでエラーになる } }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

値型と参照型の使い分け

値型とは var a = 4.0 // aに4.0が入る var b = a // bに4.0が入る(aがもつ4.0への参照ではなく、値である4.0が入る) a.fromSquareRoot() a // 2.0 b // bはaの変更の影響を受けずに4.0のまま 値型とは、インスタンスが値への参照ではなく値そのものを表す型。 参照型とは class InBox { var value: Int init(value: Int) { self.value = value } } var a = InBox(value: 1) // aはInbox(value: 1)を参照する var b = a // bはaと同じインスタンスを参照する // a.value, b.valueは両方とも1 a.value // 1 b.value // 1 // a.valueを2に変更 a.value = 2 // a.value, b.valueは両方とも2 a.value // 2 b.value // 2 参照型とは、インスタンスが値への参照を表す型です。 参照型では一つのインスタンスが複数の変数や定数の間で共有されるので、ある値に対する変更はインスタンスを共有している他の変数や定数にも伝播します。 値型と参照型の使い分け 上記を踏まえたそれぞれの性質を考慮すると、安全にデータを取り扱うためには値型、状態管理などの変更の共有が必要となる範囲には参照型を使用すると良いでしょう。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Swift] UIKitベースのプロダクトでUIをSwiftUIで開発する

課題 SwiftUIが登場して、2年ぐらい経過している。2年経つが既存のプロダクトでSwiftUIで開発をするといくつかの制約に出会す。特に機能不足、不具合、サポートバージョンなどの問題もあり、UIKitベースで進めることが多い。 しかし、長期的な視点で見るとSwiftUIが開発の中心になっていくことが予想される。今回は既存のプロダクトでUIKitとSwiftUIの共存、少しずつ置き換えるための戦略について考えてみました。 SwiftUIで実装するメリット 日頃の開発でInterface Builderを使っている場合は、コードだけでUIを実装できる 宣言的に実装できるのでメンテしやすく、コードの可読性が上がる プレビュー機能が使える(UIKitのクラスをWrapすれば同様の機能は使える) 実装方法 UIHostingControllerを使う SwiftUIのViewをUIHostingControllerに渡して、Child ViewControllerとして、登録することで表示することができる import UIKit import SwiftUI final class SampleListDisplayController: UIViewController { override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .white title = "SampleListView(SwiftUI)" let hostingController = UIHostingController(rootView: SampleListView()) addChild(hostingController) view.addSubview(hostingController.view) hostingController.didMove(toParent: self) hostingController.view.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ hostingController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor), hostingController.view.rightAnchor.constraint(equalTo: view.rightAnchor), hostingController.view.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), hostingController.view.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor) ]) } } 実装 + α Mewを使う Mewを使います Mewを使うことでchild ViewControllerの管理が楽になり、StackViewと合わせて使うとさらにレイアウトが簡単になります。 MicroViewControllerとすることで画面の一部のパーツに切り分けることができ、部分的に置き換えることが容易になります UIHostingControllerを継承して、SwiftUIのViewをrootViewに渡すことでSwiftUIのViewを表示できます。そして、MicroViewControllerとして画面に追加することで表示できます。 import Foundation import UIKit import SwiftUI import Mew final class BodyViewController: UIHostingController<BodyView>, Instantiatable, Injectable { func input(_ input: Input) {} typealias Input = Void typealias Environment = Void private var swiftUIView: BodyView var environment: Void init(with input: Input, environment: Environment) { self.swiftUIView = BodyView() super.init(rootView: swiftUIView) view.backgroundColor = .clear } @available(*, unavailable) @objc required dynamic init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } } サンプルコードはこちらです t-osawa-009/UIKit-SwiftUI まとめ 今後はSwiftUIのアップデートによって、使いやすくなり、主流になっていくことが予想される。 Mewのライブラリを使うことで少しずつの置き換えが簡単になります。 現状はSwiftUIだけでは実装したい機能を実現するのは大変である。UIKitと組み合わせ、1つの画面の一部を置き換えていくことが現実的なやり方である 環境 Xcode 12.5 Build version 12E262 Apple Swift version 5.4 (swiftlang-1205.0.26.9 clang-1205.0.19.55) Target: x86_64-apple-darwin20.3.0
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Swift] UKitベースのプロダクトでUIをSwiftUIで開発する

課題 SwiftUIが登場して、2年ぐらい経過している。2年経つが既存のプロダクトでSwiftUIで開発をするといくつかの制約にでくわす。特に機能不足、不具合、サポートバージョンなどの問題もあり、UIKitベースで進めることが多い。 しかし、長期的な視点で見るとSwiftUIが開発の中心になっていくことが予想される。今回は既存のプロダクトでUIKitとSwiftUIの共存、少しずつ置き換えるための戦略について考えてみました。 SwiftUIで実装するメリット interface builderなどを使っている場合は、コードだけでUIを実装できる 宣言的に実装できるのでメンテしやすく、コードの可読性が上がる プレビュー機能が使える(UIKitのクラスをWrapすれば同様の機能は使える) 実装方法 UIHostingControllerを使う SwiftUIのViewをUIHostingControllerに渡して、、Child ViewControllerとして、登録することで表示することができる import UIKit import SwiftUI final class SampleListDisplayController: UIViewController { override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .white title = "SampleListView(SwiftUI)" let hostingController = UIHostingController(rootView: SampleListView()) addChild(hostingController) view.addSubview(hostingController.view) hostingController.didMove(toParent: self) hostingController.view.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ hostingController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor), hostingController.view.rightAnchor.constraint(equalTo: view.rightAnchor), hostingController.view.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), hostingController.view.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor) ]) } } 実装 + α Mewを使う Mewを使います Mewを使うことでchild ViewControllerの管理が楽になり、StackViewと合わせて使うとさらにレイアウトが簡単になります。 MicroViewControllerとすることで画面の一部のパーツに切り分けることができ、部分的に置き換えることが容易になります UIHostingControllerを継承して、swiftUIViewをrootViewに渡すことでSwiftUIのViewを表示できます。あとはMicroViewControllerとして画面に追加することで表示できます。 import Foundation import UIKit import SwiftUI import Mew final class BodyViewController: UIHostingController<BodyView>, Instantiatable, Injectable { func input(_ input: Input) {} typealias Input = Void typealias Environment = Void private var swiftUIView: BodyView var environment: Void init(with input: Input, environment: Environment) { self.swiftUIView = BodyView() super.init(rootView: swiftUIView) view.backgroundColor = .clear } @available(*, unavailable) @objc required dynamic init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } } サンプルコードはこちらです t-osawa-009/UIKit-SwiftUI まとめ 今後はSwiftUIのアップデートによって、使いやすくなり、主流になっていくことが予想される。 Mewのライブラリを使うことで少しずつの置き換えが簡単になります。 現状はSwiftUIだけでは実装したい機能を実現するのは大変である。UIKitと組み合わせつつ、1つの画面の一部を置き換えていくことが現実的なやり方である 環境 Xcode 12.5 Build version 12E262 Apple Swift version 5.4 (swiftlang-1205.0.26.9 clang-1205.0.19.55) Target: x86_64-apple-darwin20.3.0
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

iOSアプリ開発で環境構築の際にmake: *** [gem-install] Error 1のエラーになる

はじめに iOSアプリ開発で環境構築の際にmake: *** [gem-install] Error 1のエラーになる場合の解決法 環境 MacBook Air(M1 2020) macOS Big Sur iOSアプリの環境構築をする際に以下のようなエラーが出ました。 エラー内容 プロジェクト名/Gemfile.lock. (Gem::GemNotFoundException) To update to the latest version installed on your system, run `bundle update --bundler`. To install the missing version, run `gem install bundler:2.1.2` make: *** [gem-install] Error 1 エラーメッセージにあるようにまずはbundle update --bundlerを実行してみます。 You must use Bundler 2 or greater with this lockfile. このlockfileを使用するには、Bundler2以上を使用する必要があります。というエラーになりました。 次にgem install bundler:2.1.2を実行してみます。 Fetching bundler-2.1.2.gem ERROR: While executing gem ... (Gem::FilePermissionError) You don't have write permissions for the /Library/Ruby/Gems/2.6.0 directory. Library/Ruby/Gems/2.6.0ディレクトリへの書き込み権限がないというエラーになりました。 上記のような流れのエラーになる場合、システムのrubyを利用しているため、権限不足でgemのインストールができない可能性が高いです。 対処法 rbenvを使用してrubyを管理します。 まずはgemとrubyがどのパスを指しているのか確認してみます。 $ which gem /usr/bin/gem $ which ruby /usr/bin/ruby rbenvをインストールしてない場合はインストールします。 $ brew install rbenv rbenvで管理されているrubyのバージョンを確認します。 $ rbenv versions * system (set by /Users/ユーザー名/.rbenv/version) 上記のようにrubyはsystemを利用しているので、rbenvの管理下にrubyをインストールします。 $ rbenv install 3.0.0 system * 3.0.0 (set by /Users/ユーザー名/.rbenv/version) globalでインストールしたバージョンを利用するように変更します。 $ rbenv global 3.0.0 rbenvで管理されたrubyに変更されているか確認します。 $ rbenv versions system * 3.0.0 (set by /Users/ユーザー名/.rbenv/version) rbenvにパスを通すため、.zshrcに以下を追加します。 [[ -d ~/.rbenv ]] && \ export PATH=${HOME}/.rbenv/bin:${PATH} && \ eval "$(rbenv init -)" ターミナルを再起動後、もう一度gemとrubyのパスを確認すると以下のように変わっています。 $ which ruby /Users/ユーザー名/.rbenv/shims/ruby $ which gem /Users/ユーザー名/.rbenv/shims/gem この状態で該当のコマンドを実行するとエラーが解決できました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

iOSアプリに画像分類モデルを導入する

この記事は何? iOSアプリでAIによる画像分類を行うために、モデルを導入する方法です。 実行環境 Swift 5.3 Xcode 12.5 macOS 11.3 使用するモデル ここでは、Apple DeveloperのWEBサイトに用意されている出来合いのモデルを使用します。 「MobileNetV2」という画像識別モデルをダウンロードします。これは、画像の中の主要なオブジェクトを分類するようにトレーニングされています。 コード ここで作成する画像分類器は、「UIImage型の画像を受け取って、分類結果の文字列を返す」という仕組みです。 画像分類クラスを定義する SwiftUIのアプリ全体で使用できる共用モデルにしておきます。 class ImageClassifier: ObservableObject { @Published var classificationLabel = "No Picture" } モデルをインスタンス化する class ImageClassifier: ObservableObject { @Published var classificationLabel = "No Picture" let model: VNCoreMLModel init() { // モデルのインスタンスを作成 let modelURL = Bundle.main.url(forResource: "MobileNetV2", withExtension: "mlmodelc")! model = try! VNCoreMLModel(for: MobileNetV2(contentsOf: modelURL).model) } } リクエストを作成する func classifications(for image: UIImage) { classificationLabel = "Classifying..." // 写真の方向を取得する let orientation = CGImagePropertyOrientation(rawValue: UInt32(image.imageOrientation.rawValue)) // UIImage型をCIImage型に変換 guard let ciImage = CIImage(image: image) else { fatalError("Unable to create \(CIImage.self) from \(image).") } // グローバルキューでハンドラを作成して実行 DispatchQueue.global(qos: .userInitiated).async { // リクエストを作成 let request = VNCoreMLRequest(model: self.model, completionHandler: { (request, error) in self.processClassifications(for: request, error: error) // リクエストの完了ハンドラ }) // 写真のをモデル指定のサイズに切り取る request.imageCropAndScaleOption = .centerCrop // リクエストハンドラを作成して、リクエストを実行する let handler = VNImageRequestHandler(ciImage: ciImage, orientation: orientation!) try! handler.perform([request]) } } リクエストのコールバック func processClassifications(for request: VNRequest, error: Error?) { // メインキューでリクエストの結果を処理する DispatchQueue.main.async { guard let results = request.results else { self.classificationLabel = "Unable to classify image.\n\(error!.localizedDescription)" return } // resultsの型は常に、VNClassificationObservationのコレクション let classifications = results as! [VNClassificationObservation] if classifications.isEmpty { self.classificationLabel = "Nothing recognized." } else { // 確度が高い順に分類を表示する let topClassifications = classifications.prefix(2) let descriptions = topClassifications.map { classification in // 分類の表示形式: "(0.37) cliff, drop, drop-off" return String(format: "(%.2f) %@", classification.confidence, classification.identifier) } // Published属性プロパティを変更して、ビューのラベルを更新 self.classificationLabel = descriptions.joined(separator: "\n") } } } 全体のコード import Foundation import Vision import UIKit class ImageClassifier: ObservableObject { @Published var classificationLabel = "No Picture" let model: VNCoreMLModel init() { // モデルのインスタンスを作成 let modelURL = Bundle.main.url(forResource: "MobileNetV2", withExtension: "mlmodelc")! model = try! VNCoreMLModel(for: MobileNetV2(contentsOf: modelURL).model) } func classifications(for image: UIImage) { classificationLabel = "Classifying..." // 写真の方向を取得する let orientation = CGImagePropertyOrientation(rawValue: UInt32(image.imageOrientation.rawValue)) // UIImage型をCIImage型に変換 guard let ciImage = CIImage(image: image) else { fatalError("Unable to create \(CIImage.self) from \(image).") } // グローバルキューでハンドラを作成して実行 DispatchQueue.global(qos: .userInitiated).async { // リクエストを作成 let request = VNCoreMLRequest(model: self.model, completionHandler: { (request, error) in self.processClassifications(for: request, error: error) // リクエストの完了ハンドラ }) // 写真のをモデル指定のサイズに切り取る request.imageCropAndScaleOption = .centerCrop // リクエストハンドラを作成して、リクエストを実行する let handler = VNImageRequestHandler(ciImage: ciImage, orientation: orientation!) try! handler.perform([request]) } } // リクエストのコールバックでやること func processClassifications(for request: VNRequest, error: Error?) { // メインキューでリクエストの結果を処理する DispatchQueue.main.async { guard let results = request.results else { self.classificationLabel = "Unable to classify image.\n\(error!.localizedDescription)" return } // resultsの型は常に、VNClassificationObservationのコレクション let classifications = results as! [VNClassificationObservation] if classifications.isEmpty { self.classificationLabel = "Nothing recognized." } else { // 確度が高い順に分類を表示する let topClassifications = classifications.prefix(2) let descriptions = topClassifications.map { classification in // 分類の表示形式: "(0.37) cliff, drop, drop-off" String(format: "(%.2f) %@", classification.confidence, classification.identifier) } // Published属性プロパティを変更して、ビューのラベルを更新 self.classificationLabel = descriptions.joined(separator: "\n") } } } }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SwiftUIアプリに画像分類モデルを導入する

この記事は何? iOSアプリでAIによる画像分類を行うために、モデルを導入する方法です。 実行環境 Swift 5.3 Xcode 12.5 macOS 11.3 使用するモデル ここでは、Apple DeveloperのWEBサイトに用意されている出来合いのモデルを使用します。 「MobileNetV2」という画像識別モデルをダウンロードします。これは、画像の中の主要なオブジェクトを分類するようにトレーニングされています。 コード ここで作成する画像分類器は、「UIImage型の画像を受け取って、分類結果の文字列を返す」という仕組みです。 画像分類クラスを定義する SwiftUIのアプリ全体で使用できる共用モデルにしておきます。 class ImageClassifier: ObservableObject { @Published var classificationLabel = "No Picture" } モデルをインスタンス化する class ImageClassifier: ObservableObject { @Published var classificationLabel = "No Picture" let model: VNCoreMLModel init() { // モデルのインスタンスを作成 let modelURL = Bundle.main.url(forResource: "MobileNetV2", withExtension: "mlmodelc")! model = try! VNCoreMLModel(for: MobileNetV2(contentsOf: modelURL).model) } } リクエストを作成する func classifications(for image: UIImage) { classificationLabel = "Classifying..." // 写真の方向を取得する let orientation = CGImagePropertyOrientation(rawValue: UInt32(image.imageOrientation.rawValue)) // UIImage型をCIImage型に変換 guard let ciImage = CIImage(image: image) else { fatalError("Unable to create \(CIImage.self) from \(image).") } // グローバルキューでハンドラを作成して実行 DispatchQueue.global(qos: .userInitiated).async { // リクエストを作成 let request = VNCoreMLRequest(model: self.model, completionHandler: { (request, error) in self.processClassifications(for: request, error: error) // リクエストの完了ハンドラ }) // 写真のをモデル指定のサイズに切り取る request.imageCropAndScaleOption = .centerCrop // リクエストハンドラを作成して、リクエストを実行する let handler = VNImageRequestHandler(ciImage: ciImage, orientation: orientation!) try! handler.perform([request]) } } リクエストのコールバック func processClassifications(for request: VNRequest, error: Error?) { // メインキューでリクエストの結果を処理する DispatchQueue.main.async { guard let results = request.results else { self.classificationLabel = "Unable to classify image.\n\(error!.localizedDescription)" return } // resultsの型は常に、VNClassificationObservationのコレクション let classifications = results as! [VNClassificationObservation] if classifications.isEmpty { self.classificationLabel = "Nothing recognized." } else { // 確度が高い順に分類を表示する let topClassifications = classifications.prefix(2) let descriptions = topClassifications.map { classification in // 分類の表示形式: "(0.37) cliff, drop, drop-off" return String(format: "(%.2f) %@", classification.confidence, classification.identifier) } // Published属性プロパティを変更して、ビューのラベルを更新 self.classificationLabel = descriptions.joined(separator: "\n") } } } 全体のコード import Foundation import Vision import UIKit class ImageClassifier: ObservableObject { @Published var classificationLabel = "No Picture" let model: VNCoreMLModel init() { let modelURL = Bundle.main.url(forResource: "MobileNetV2", withExtension: "mlmodelc")! model = try! VNCoreMLModel(for: MobileNetV2(contentsOf: modelURL).model) } func classifications(for image: UIImage) { classificationLabel = "Classifying..." let orientation = CGImagePropertyOrientation(rawValue: UInt32(image.imageOrientation.rawValue)) guard let ciImage = CIImage(image: image) else { fatalError("Unable to create \(CIImage.self) from \(image).") } DispatchQueue.global(qos: .userInitiated).async { let request = VNCoreMLRequest(model: self.model, completionHandler: { (request, error) in self.processClassifications(for: request, error: error) }) request.imageCropAndScaleOption = .centerCrop let handler = VNImageRequestHandler(ciImage: ciImage, orientation: orientation!) try! handler.perform([request]) } } // リクエストのコールバックでやること func processClassifications(for request: VNRequest, error: Error?) { DispatchQueue.main.async { guard let results = request.results else { self.classificationLabel = "Unable to classify image.\n\(error!.localizedDescription)" return } let classifications = results as! [VNClassificationObservation] if classifications.isEmpty { self.classificationLabel = "Nothing recognized." } else { let topClassifications = classifications.prefix(2) let descriptions = topClassifications.map { classification in String(format: "(%.2f) %@", classification.confidence, classification.identifier) } self.classificationLabel = descriptions.joined(separator: "\n") } } } }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

addTargetメソッドの基礎的な理解

たぶん初学者でもaddTargetメソッドを実装しようと思えば、エラーで躓くことはあっても、なんとなく実装できてしまうものだと思います。でも、少し時間をおいてしまうとたぶんまたよくわからないままに実装することになります。ここにいたってきちんと理解しなければと気づくのですが、たぶん問題はaddTargetそのものではなくて、それぞれのパラメータが意味する部分やクラスのプロパティやメソッドの表現の仕方、そうした基礎的な文法、言語の知識の不足からくるものではないかと考えます。とさも当然のように自分の経験を一般化して話をしていますが、ここにいたってのまさにここにいるのがワイです。なにもかも、ワイの頭のコンパイラが悪いんだ。 このQiitaでは、iOSアプリ開発で基本的なインスタンスメソッドであるaddTargetをそれぞれのパラメータの意味や理解を通じて把握していくことを目指します。 addTarget(_:action:for:) func addTarget(_ target: Any?, action: Selector, for controlEvents: UIControl.Event) サンプル override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. let button = UIButton(type: .system) button.setTitle("Button", for: .normal) button.setTitleColor(UIColor.white, for: .normal) button.backgroundColor = UIColor.black button.frame = CGRect(x: view.center.x-50, y: view.center.y-50, width: 100, height: 100) button.translatesAutoresizingMaskIntoConstraints = false self.view.addSubview(button) //次の一文 button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside) } @objc func buttonTapped(){ print("buttonがタップされました") } まずインスタンスメソッドであるため、実装にはインスタンスを用意する必要があります。ここでは省きますが、上記のbuttonをUIButtonのインスタンスとしてコード上で記載しています。 Parameters target   ドキュメントにはThe target object—that is, the object whose action method is called.と書かれており、actionメソッドが属しているオブジェクトを指定するパラメータとなります。逆に言えば、そのオブジェクトのメソッドを呼ぶということです。Any型のオプショナルであるため、基本的にどんな型でも指定できます。とはいえネットに上がっているものはサンプルが多く、そのViewController内に記載されているため、たいていはselfとして呼ばれています。 またnilも許容されており、その場合はactionやObjectへの nilである場合はUIKitがオブジェクトに対するactionメッセージを特定し伝えられとされています。 試しに上記サンプルコードでnilにしてみましたが、コンパイルも実行も問題なく行えました。 button.addTarget(nil, action: #selector(buttonTapped), for: .touchUpInside) action actionメソッドを特定するセレクタ。ここでどのメソッドをactionとするかを設定する。 一般にはList1の表現とマッチするようなセレクターが指定される。 nilは許されないため、必ず指定しなければならない。 Listing 1 Action method signatures @IBAction func doSomething() @IBAction func doSomething(sender: UIButton) @IBAction func doSomething(sender: UIButton, forEvent event: UIEvent) *sender は送信者という意味 controlEvents actionをコントロールする具体的なイベントを示すビットマスク。 常に少なくとも1つの定数を指定しなければならない。 要するにタッチするとか、押し続けるとかそうした操作(=イベント)を示せということ。 そうした操作は定数として記述されているから、定数を指定なんてことになるわけですね。 Selectorとは ちなみにセレクタ(Selector)とは、Swiftの前身であるObjective-Cの概念です。 といっても、Swiftという言語の純粋な仕様にはないだけであり、他の言語一般にはセレクタは存在します。またSwiftでもセレクタを必要とする場合はあるため、こうした部分はObjective-Cを通して利用しています。Objective-Cを利用する場合には。セレクタが取るパラメータに@objcという属性を付けなければいけません。時々みかける@objcというのはObjective-Cの略称なんですね。 Selector Expression 実際のセレクタはどのように表現するかというと、 #selector(method name) #selector(getter: property name) #selector(setter: property name) もちろんセレクタはObjective-Cの概念であるため、そのランタイム時に利用できるように上記メソッドやプロパティの名称に@objcを付けて参照できるようにしなければなりません。 Selectorの書き方 書き方としては ・#Selector ・Selector ・文字列のみ の3つの方法がありますが、実際に使用されているのは#Selectorです。#Selectorにはコンパイラチェックが働きエラーが表示されるのですが、他2つに関しては表示されずエラーは実行時エラーのみです。#Selectorが使われるのはこのような理由からだと思われます。 またSelectorがパラメータとして受け取れるのは2つまでで、UIButtonとUIEventに限られるのだそうです。たしかにExpressionの部分でも表記したとおり、下記の表現は最大でsenderとeventの指定のみとなっています。 Listing 1 Action method signatures @IBAction func doSomething(sender: UIButton, forEvent event: UIEvent) UIControl.Eventの種類 UIcontrolとは class UIControl : UIView UIViewを継承したクラスです。名前の通り、user interactionに対する操作や指示を伝えます。 UIControl.Eventとは struct Event Event自体はstruct型、構造体です。そしてその構造体に含まれるプロパティは次の通りです。 UIKitはUIパーツの初期設定と言えるので、そのプロパティもすべてスタティックで、Event型に紐付いています。よく使われているのはtouchUpInsideです。コメントにあるように、指がコントロールの内部をタッチアップ、触れたときをイベントとして扱うプロパティです。 addTargetのパラメータの型はUIcontrol.Eventと構造体までの型が指定されているので、記述は .touchUpInside と省略できます。 static var touchDown: UIControl.Event //A touch-down event in the control. static var touchDownRepeat: UIControl.Event //A repeated touch-down event in the control; for this event the value of the UITouch tapCount method is greater than one. static var touchDragInside: UIControl.Event //An event where a finger is dragged inside the bounds of the control. static var touchDragOutside: UIControl.Event //An event where a finger is dragged just outside the bounds of the control. static var touchDragEnter: UIControl.Event//An event where a finger is dragged into the bounds of the control. static var touchDragExit: UIControl.Event //An event where a finger is dragged from within a control to outside its bounds. static var touchUpInside: UIControl.Event //A touch-up event in the control where the finger is inside the bounds of the control. static var touchUpOutside: UIControl.Event //A touch-up event in the control where the finger is outside the bounds of the control. static var touchCancel: UIControl.Event //A system event canceling the current touches for the control. static var valueChanged: UIControl.Event //A touch dragging or otherwise manipulating a control, causing it to emit a series of different values. static var menuActionTriggered: UIControl.Event //A menu action has triggered prior to the menu being presented. static var primaryActionTriggered: UIControl.Event //A semantic action triggered by buttons. static var editingDidBegin: UIControl.Event //A touch initiating an editing session in a UITextField object by entering its bounds. static var editingChanged: UIControl.Event //A touch making an editing change in a UITextField object. static var editingDidEnd: UIControl.Event //A touch ending an editing session in a UITextField object by leaving its bounds. static var editingDidEndOnExit: UIControl.Event //A touch ending an editing session in a UITextField object. static var allTouchEvents: UIControl.Event //All touch events. static var allEditingEvents: UIControl.Event //All editing touches for UITextField objects. static var applicationReserved: UIControl.Event //A range of control-event values available for application use. static var systemReserved: UIControl.Event //A range of control-event values reserved for internal framework use. static var allEvents: UIControl.Event //All events, including system events. 所感  addTargetひとつとっても、単にそのメソッドがなにをするメソッドなのかではなくて、それ以前になにを指定しているのか、指定するためにはなにを必要とするのか、どうしてそのような書き方をしなければならないのかといったことがわからないと、うまく飲み込めなかったため、まとめてみました。すべてを覚えることは土台無理ですので、UIcontrolなども覚えるべきものではなくて、一通り知っておく、あるいは調べて読めばわかる程度でいいはずです。ただ、そのためには基本となる言語の背景や文法的な知識が不可欠だと思いました。というか文系脳であれなんですが、英文解釈みたいだ。 参考 addTarget(_:action:for:) https://developer.apple.com/documentation/uikit/uicontrol/1618259-addtarget Selector完全攻略、そして初学者特有のAddTarget()やAddObserver()のセレクタに変数を渡そうとする願望について https://qiita.com/st43/items/43c5c0f5e1f727ba0ff0#selector
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む