- 投稿日:2020-05-24T23:43:55+09:00
【iOS】XCUIElementTypeQueryProviderの定義済みクエリとUI要素の対応
XCUITestでUITestを作成する場合、UI要素の検索でXCUIElementTypeQueryProviderの定義済みクエリを
使うこともあると思います。
定義済みクエリを使用する時、クエリと実際のUI要素との対応が一部曖昧な部分があったので、
よく使いそうなUI要素との対応を備忘録的に記録しておこうと思います。UILabel
func test_UILabelを取得() { let label = app.staticTexts["UILabel"] // accessibility identifierを指定 XCTAssertTrue(label.exists) // 指定したaccessibility identifierのUI要素が存在するか確認 }UIImageView
func test_UIImageViewを取得() { let imageView = app.images["UIImageView"] XCTAssertTrue(imageView.exists) }UITextField(secureTextEntryではない)
func test_UITextFieldを取得() { let textField = app.textFields["UITextField"] XCTAssertTrue(textField.exists) }UITextField(secureTextEntryである)
func test_secureTextEntryのUITextFieldを取得() { let secureTextField = app.secureTextFields["UITextField_Secure"] XCTAssertTrue(secureTextField.exists) }UIButton
func test_UIButtonを取得() { let button = app.buttons["UIButton"] XCTAssertTrue(button.exists) }UITextView
func test_UITextViewを取得() { let textView = app.textViews["UITextView"] XCTAssertTrue(textView.exists) }UISegmentedControl
func test_UISegmentedControlを取得() { let segmentedControl = app.segmentedControls["SegmentedControl"] XCTAssertTrue(segmentedControl.exists) }UISlider
func test_UISliderを取得() { let slider = app.sliders["UISlider"] XCTAssertTrue(slider.exists) }UISwitch
func test_UISwitchを取得() { let uiSwitch = app.switches["UISwitch"] XCTAssertTrue(uiSwitch.exists) }UIView
func test_UIViewを取得() { let view = app.otherElements["UIView"] XCTAssertTrue(view.exists) }終わりに
上記のUI以外のクエリは以下のリンクをご参照ください。
定義済みクエリを全て確認できます。
クエリはiOS, macOSの区別なく定義されているので一部iOSでは使えないものもあります。(touchBarなど)
https://developer.apple.com/documentation/xctest/xcuielementtypequeryprovider
- 投稿日:2020-05-24T23:01:11+09:00
R.Swiftでビルドエラー(Pods/R.swift/rswift: No such file or directory)が発生
事象
これです。よく見るやつですね。
Pods/R.swift/rswift: No such file or directory Command PhaseScriptExecution failed with a nonzero exit codeGoogle先生に聞けばCleanすれば直るとか諸々削除してpod installすれば直るとかBuildConfigの設定が間違っているとか、色々な解消法が出てくるかと思います。
今回、数分前までBuildできていたものが、いきなりBuildできなくなり
一般的な解消方法を全部やってもダメで、数時間潰しました。■一般的な方法 (引用元 https://qiita.com/masa-321/items/19f66557dcff65553f18)
・Clean(Command + Shift + K) ・DerivedDataファイル以下全ての中間ファイル(キャッシュ?)を削除 参考:[Xcode][小ネタ] DerivedDataの削除についての備忘録 ・Podfileを編集して、Podの再インストール。 ・最近アップデートしたライブラリがあれば、ダウングレードして試してみる。 ・Cocoa Podsのアップデート ・Carthageのアップデート ・キーチェーンのログインをロック&解除を1度繰り返す ・古いMacbookの証明書をコピー 参考:iOSアプリ開発で実機による開発を複数台(メイン機ではない2台目以降)のMacで行いたい場合 ・Build Phases>Run Script>Shellにて、タイピングミスがないか(余計なスペースが入っているケースがある)・・・① ・Xcodeの再起動 ・Macbook本体の再起動原因
アンチウイルスソフトが生成された
Pods/R.swift/rswift
を生成される度に消してました。
F**k
- 投稿日:2020-05-24T23:01:11+09:00
R.swiftでビルドエラー(Pods/R.swift/rswift: No such file or directory)が発生
事象
これです。よく見るやつですね。
Pods/R.swift/rswift: No such file or directory Command PhaseScriptExecution failed with a nonzero exit codeGoogle先生に聞けばCleanすれば直るとか諸々削除してpod installすれば直るとかBuildConfigの設定が間違っているとか、色々な解消法が出てくるかと思います。
今回、数分前までBuildできていたものが、いきなりBuildできなくなり
一般的な解消方法を全部やってもダメで、数時間潰しました。■一般的な方法 (引用元 https://qiita.com/masa-321/items/19f66557dcff65553f18)
・Clean(Command + Shift + K) ・DerivedDataファイル以下全ての中間ファイル(キャッシュ?)を削除 参考:[Xcode][小ネタ] DerivedDataの削除についての備忘録 ・Podfileを編集して、Podの再インストール。 ・最近アップデートしたライブラリがあれば、ダウングレードして試してみる。 ・Cocoa Podsのアップデート ・Carthageのアップデート ・キーチェーンのログインをロック&解除を1度繰り返す ・古いMacbookの証明書をコピー 参考:iOSアプリ開発で実機による開発を複数台(メイン機ではない2台目以降)のMacで行いたい場合 ・Build Phases>Run Script>Shellにて、タイピングミスがないか(余計なスペースが入っているケースがある)・・・① ・Xcodeの再起動 ・Macbook本体の再起動原因
アンチウイルスソフトが生成された
Pods/R.swift/rswift
を生成される度に消してました。
F**k
- 投稿日:2020-05-24T22:41:57+09:00
iOSのBageを標準とは違う位置に無理やりつける方法
ごくまれに、TabBarのバッチの位置をカスタムしたいとお願いされることがあります。
無理やりバッチをつけるのはちょっとめんどくさいですが、上手くできたので、メモっておきます。class CustomTabViewController: UITabBarController { private var badges: [UIView] = [] override func viewDidLoad() { super.viewDidLoad() addBage(position: 3, text: "11", frame: CGRect(x: 30, y: 5, width: 18, height: 18)) } extension MainTabViewController { // バッチをつける private func addBage(position: Int, text:String, frame:CGRect) { guard tabBar.subviews.count > position else { return } let tabBarButton = tabBar.subviews[position] for subView in tabBarButton.subviews { guard let icon = subView as? UIImageView else { continue } let badge = createBage(text: text, frame: frame) icon.addSubview(badge) badges.append(badge) } } // バッチを全て剥がす private func removeAllBage() { badges.forEach { bage in bage.removeFromSuperview() } } // バッジを生成 private func createBage(text: String, frame: CGRect) -> UIView { let bageView = UILabel(frame: frame) bageView.text = text bageView.textAlignment = .center bageView.font = .systemFont(ofSize: 13) bageView.adjustsFontSizeToFitWidth = true bageView.minimumScaleFactor = 0.5 bageView.textColor = .white bageView.layer.cornerRadius = 9 bageView.layer.masksToBounds = true bageView.backgroundColor = .red return bageView } } }
- 投稿日:2020-05-24T13:25:41+09:00
【swift 5】 dateFormat の多国籍対応
アプリを多国籍対応する時に、年月日の表示を国別に変える必要があります。
日本人が使う時は、2020年5月24日
アメリカ人が使う時は、May 24, 2020
と表示させるには以下のようにします。let formatter = DateFormatter() formatter.dateFormat = DateFormatter.dateFormat(fromTemplate: "ydMMM", options: 0, locale: .autoupdatingCurrent)locale: .autoupdatingCurrent とすることでユーザーが選んだ「使用する言語の優先順序」第一位の言語が選択されます。
- 投稿日:2020-05-24T13:25:41+09:00
【swift 5】 dateFormat の多言語対応
アプリを多言語対応する時に、年月日の表示を国別に変える必要があります。
日本人が使う時は、2020年5月24日
アメリカ人が使う時は、May 24, 2020
と表示させるには以下のようにします。let formatter = DateFormatter() formatter.dateFormat = DateFormatter.dateFormat(fromTemplate: "ydMMM", options: 0, locale: .autoupdatingCurrent)locale: .autoupdatingCurrent とすることでユーザーが選んだ「使用する言語の優先順序」第一位の言語が選択されます。
- 投稿日:2020-05-24T13:14:22+09:00
[Swift / Xcode / Realm]Realm を利用した ToDoアプリの実装(ToDo の追加・編集・更新・削除)
この記事の内容
モバイル向けデータベース管理システム Realm を利用した、ToDo アプリの開発
参考にした記事
Realmを使ってTODOアプリを作ってみよう!/ Swift(Realmの使い方、初級編)
開発したアプリ
前掲の記事を参考にして、ToDo を追加しテーブルビューで表示することに加えて下記機能を実装した。
GitHub にもコードを UP しています。
GitHub - tanakadaichi1989・Remove All ボタン をクリックすると、ToDo を全て削除
・ToDo を表示しているテーブルビューのセルを左にスライドすると、その ToDo のみを削除
・ToDo を表示しているテーブルビューのセルをクリックすると、ToDo を編集するためのアラートコントローラを表示
・アラートコントローラの OKボタン をクリックすると、ToDo を更新
前提条件
① CocoaPods Realm の導入、テキストボックスに入力した文字を保存する処理を実装する方法は前掲の記事を参照してください。
② 今回のアプリは、Realm の利用方法を確認するために開発しました。その為、 Apple が策定した Human Interface Guidelines は考慮していません。
開発環境
項目 PC MacBook Air 2017 IDE Xcode Ver. 11.4.1 言語 Swift 5.2.2 データベース Realm コード管理 GitHub Realm とは
モバイル向けデータベース管理システム
Main.storyboard
項目 属性 備考 ViewCotroller UIViewController ViewCotroller.swift と紐付け Navigation Bar UINavigationBar Title を "TestApp" と設定 ToDoTextField UITextField このテキストフィールドに入力した文字を ToDo として保存 Add Button UIButton ボタンの色:青 角は丸く Remove All Button UIButton ボタンの色:赤 角は丸く ToDoTableView UITableView 保存されている ToDo を表示 testCell UITableViewCell Identifier を "testCell" と設定 コード
ToDoModel.swiftimport Foundation import RealmSwift class TodoModel: Object{ @objc dynamic var todo: String? = nil }ViewCotroller.swiftimport UIKit import RealmSwift class ViewController: UIViewController,UITextFieldDelegate,UITableViewDelegate { @IBOutlet weak var todoTextFiled: UITextField! @IBOutlet weak var todoTableView: UITableView! @IBOutlet weak var addButton: UIButton! @IBOutlet weak var removeAllButton: UIButton! var itemList: Results<TodoModel>! // Add ボタンをクリックした際に実行する処理 @IBAction func tapAddButton(_ sender: Any) { let instancedTodoModel:TodoModel = TodoModel() instancedTodoModel.todo = self.todoTextFiled.text let realmInstance = try! Realm() try! realmInstance.write{ realmInstance.add(instancedTodoModel) } self.todoTableView.reloadData() } // Remove All ボタンをクリックした際に実行する処理 @IBAction func tapRemoveAllButton(_ sender: Any) { let realmInstance = try! Realm() try! realmInstance.write{ realmInstance.deleteAll() } self.todoTableView.reloadData() } override func viewDidLoad() { super.viewDidLoad() // UITableViewDataSource を self に設定 self.todoTableView.dataSource = self // UITableViewDelegate を self に設定 self.todoTableView.delegate = self // ボタンの角を丸くする設定 addButton.layer.cornerRadius = 5 removeAllButton.layer.cornerRadius = 5 let realmInstance = try! Realm() self.itemList = realmInstance.objects(TodoModel.self) self.todoTableView.reloadData() } } extension ViewController: UITableViewDataSource{ func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return self.itemList.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let testCell:UITableViewCell = tableView.dequeueReusableCell(withIdentifier: "testCell")! let item: TodoModel = self.itemList[(indexPath as NSIndexPath).row] testCell.textLabel?.text = item.todo return testCell } // テーブルビューの編集を許可 func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { return true } // テーブルビューのセルとデータを削除 func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) { if editingStyle == UITableViewCell.EditingStyle.delete { // データを削除 let realmInstance = try! Realm() try! realmInstance.write { realmInstance.delete(itemList[indexPath.row]) } // セルを削除 tableView.deleteRows(at: [indexPath as IndexPath], with: UITableView.RowAnimation.automatic) } } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { print("セル\(indexPath)が選択されました") showAlertController(indexPath) } // テーブルビューのセルをクリックしたら、アラートコントローラを表示する処理 func showAlertController(_ indexPath: IndexPath){ let alertController: UIAlertController = UIAlertController(title: "\(String(indexPath.row))番目の ToDo を編集", message: itemList[indexPath.row].todo, preferredStyle: .alert) // アラートコントローラにテキストフィールドを表示 テキストフィールドには入力された情報を表示させておく処理 alertController.addTextField(configurationHandler: {(textField: UITextField!) in textField.text = self.itemList[indexPath.row].todo}) // アラートコントローラに"OK"ボタンを表示 "OK"ボタンをクリックした際に、テキストフィールドに入力した文字で更新する処理を実装 alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: { (action) -> Void in self.updateAlertControllerText(alertController,indexPath) })) // アラートコントローラに"Cancel"ボタンを表示 alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) self.present(alertController, animated: true, completion: nil) } // "OK"ボタンをクリックした際に、テキストフィールドに入力した文字で更新 func updateAlertControllerText(_ alertcontroller:UIAlertController, _ indexPath: IndexPath) { // guard を利用して、nil チェック guard let textFields = alertcontroller.textFields else {return} guard let text = textFields[0].text else {return} // UIAlertController に入力された文字をコンソールに出力 print(text) // Realm に保存したデータを UIAlertController に入力されたデータで更新 let realmInstance = try! Realm() try! realmInstance.write{ itemList[indexPath.row].todo = text } self.todoTableView.reloadData() } }実装のポイント
・テーブルビューのセルを削除するだけでは、それに紐づく Realm に保存したデータは削除されない。そのため、セルとデータをそれぞれ削除する処理を実装する必要がある。
感想
・CocoaPods Realm の導入に少しつまづいた
・アラートコントロールを利用した編集・更新処理のコードが煩雑になったので、リファクタリングしたい
・データベースと連携した iOS アプリを開発できるようになり、開発の幅が広まったと思う
・Human Interface Guidelines に沿うように UI を改善したい
- 投稿日:2020-05-24T13:14:22+09:00
Realmを利用したiOSアプリの実装(データの追加・編集・更新・削除)
この記事の内容
モバイル向けデータベース管理システム Realm を利用した、iOSアプリの開発
参考にした記事
Realmを使ってTODOアプリを作ってみよう!/ Swift(Realmの使い方、初級編)
開発したアプリ
前掲の記事を参考にして、ToDo を追加しテーブルビューで表示することに加えて下記機能を実装した。
・Remove All ボタン をクリックすると、ToDo を全て削除
・ToDo を表示しているテーブルビューのセルを左にスライドすると、その ToDo のみを削除
・ToDo を表示しているテーブルビューのセルをクリックすると、ToDo を編集するためのアラートコントローラを表示
・アラートコントローラの OKボタン をクリックすると、ToDo を更新
前提条件
① CocoaPods Realm の導入、テキストボックスに入力した文字を保存する処理を実装する方法は前掲の記事を参照してください。
② 今回のアプリは、Realm の利用方法を確認するために開発しました。その為、 Apple が策定した Human Interface Guidelines は考慮していません。
開発環境
項目 PC MacBook Air 2017 IDE Xcode Ver. 11.4.1 言語 Swift 5.2.2 データベース Realm コード管理 GitHub Realm とは
モバイル向けデータベース管理システム
Main.storyboard
項目 属性 備考 ViewCotroller UIViewController ViewCotroller.swift と紐付け Navigation Bar UINavigationBar Title を "TestApp" と設定 ToDoTextField UITextField このテキストフィールドに入力した文字を ToDo として保存 Add Button UIButton ボタンの色:青 角は丸く Remove All Button UIButton ボタンの色:赤 角は丸く ToDoTableView UITableView 保存されている ToDo を表示 testCell UITableViewCell Identifier を "testCell" と設定 コード
ToDoModel.swiftimport Foundation import RealmSwift class TodoModel: Object{ @objc dynamic var todo: String? = nil }ViewCotroller.swiftimport UIKit import RealmSwift class ViewController: UIViewController,UITextFieldDelegate,UITableViewDelegate { @IBOutlet weak var todoTextFiled: UITextField! @IBOutlet weak var todoTableView: UITableView! @IBOutlet weak var addButton: UIButton! @IBOutlet weak var removeAllButton: UIButton! var itemList: Results<TodoModel>! // Add ボタンをクリックした際に実行する処理 @IBAction func tapAddButton(_ sender: Any) { let instancedTodoModel:TodoModel = TodoModel() instancedTodoModel.todo = self.todoTextFiled.text let realmInstance = try! Realm() try! realmInstance.write{ realmInstance.add(instancedTodoModel) } self.todoTableView.reloadData() } // Remove All ボタンをクリックした際に実行する処理 @IBAction func tapRemoveAllButton(_ sender: Any) { let realmInstance = try! Realm() try! realmInstance.write{ realmInstance.deleteAll() } self.todoTableView.reloadData() } override func viewDidLoad() { super.viewDidLoad() // UITableViewDataSource を self に設定 self.todoTableView.dataSource = self // UITableViewDelegate を self に設定 self.todoTableView.delegate = self // ボタンの角を丸くする設定 addButton.layer.cornerRadius = 5 removeAllButton.layer.cornerRadius = 5 let realmInstance = try! Realm() self.itemList = realmInstance.objects(TodoModel.self) self.todoTableView.reloadData() } } extension ViewController: UITableViewDataSource{ func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return self.itemList.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let testCell:UITableViewCell = tableView.dequeueReusableCell(withIdentifier: "testCell")! let item: TodoModel = self.itemList[(indexPath as NSIndexPath).row] testCell.textLabel?.text = item.todo return testCell } // テーブルビューの編集を許可 func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { return true } // テーブルビューのセルとデータを削除 func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) { if editingStyle == UITableViewCell.EditingStyle.delete { // データを削除 let realmInstance = try! Realm() try! realmInstance.write { realmInstance.delete(itemList[indexPath.row]) } // セルを削除 tableView.deleteRows(at: [indexPath as IndexPath], with: UITableView.RowAnimation.automatic) } } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { print("セル\(indexPath)が選択されました") showAlertController(indexPath) } // テーブルビューのセルをクリックしたら、アラートコントローラを表示する処理 func showAlertController(_ indexPath: IndexPath){ let alertController: UIAlertController = UIAlertController(title: "\(String(indexPath.row))番目の ToDo を編集", message: itemList[indexPath.row].todo, preferredStyle: .alert) // アラートコントローラにテキストフィールドを表示 テキストフィールドには入力された情報を表示させておく処理 alertController.addTextField(configurationHandler: {(textField: UITextField!) in textField.text = self.itemList[indexPath.row].todo}) // アラートコントローラに"OK"ボタンを表示 "OK"ボタンをクリックした際に、テキストフィールドに入力した文字で更新する処理を実装 alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: { (action) -> Void in self.updateAlertControllerText(alertController,indexPath) })) // アラートコントローラに"Cancel"ボタンを表示 alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) self.present(alertController, animated: true, completion: nil) } // "OK"ボタンをクリックした際に、テキストフィールドに入力した文字で更新 func updateAlertControllerText(_ alertcontroller:UIAlertController, _ indexPath: IndexPath) { // guard を利用して、nil チェック guard let textFields = alertcontroller.textFields else {return} guard let text = textFields[0].text else {return} // UIAlertController に入力された文字をコンソールに出力 print(text) // Realm に保存したデータを UIAlertController に入力されたデータで更新 let realmInstance = try! Realm() try! realmInstance.write{ itemList[indexPath.row].todo = text } self.todoTableView.reloadData() } }実装のポイント
・テーブルビューのセルを削除するだけでは、それに紐づく Realm に保存したデータは削除されない。そのため、セルとデータをそれぞれ削除する処理を実装する必要がある。
感想
・CocoaPods Realm の導入に少しつまづいた
・アラートコントロールを利用した編集・更新処理のコードが煩雑になったので、リファクタリングしたい
・データベースと連携した iOS アプリを開発できるようになり、開発の幅が広まったと思う
・Human Interface Guidelines に沿うように UI を改善したい
- 投稿日:2020-05-24T12:03:34+09:00
【Swift5】自作クラス(と配列)をUserDefaultsに簡単に保存する
はじめに
初めまして。Lukeです。
この記事では自作クラスをUserDefaultsに簡単に保存する関数を紹介します。
ちょっとしたデータの保存はUserDefaultsを使うのが一般的ですが、保存できる型が限られています。
対応していない場合はDataに変換して保存する必要があり、少し面倒です。
そこで今回は関数(と言っても大したことはないですが)を作成しました。保存したいクラス
class Friend: NSObject, NSCoding { var name:String? var age:Int? init(_ name: String?, _age: Int?) { self.name = name self.age= age } func encode(with coder: NSCoder) { coder.encode(name, forKey: "name") coder.encode(age, forKey: "age") } required init?(coder: NSCoder) { name = coder.decodeObject(forKey: "name") as? String age = coder.decodeObject(forKey: "age") as? Int } }ポイントは1行目に
: NSObject, NSCoding
と加えることと、エンコード・デコードの部分を用意することです。保存、取得用の関数
通常
extension UserDefaults { func getFriend(_ key:String) -> Friend? { if let storedData = UserDefaults.standard.object(forKey: key) as? Data { if let unarchivedObject = try! NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(storedData) as? Friend { return unarchivedObject } } return nil } func setFriend(_ friend:[Friend],_ key:String) { let data = try! NSKeyedArchiver.archivedData(withRootObject: friend, requiringSecureCoding: false) UserDefaults.standard.set(data, forKey: key) } }配列の場合
extension UserDefaults { func getFriends(_ key:String) -> [Friend]? { if let storedData = self.object(forKey: key) as? Data { if let unarchivedObject = try! NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(storedData) as? [Friend] { return unarchivedObject } } return nil } func setFriends(_ friends:[Friend],_ key:String) { let data = try! NSKeyedArchiver.archivedData(withRootObject: friends, requiringSecureCoding: false) self.set(data, forKey: key) } }使用方法
この関数はUserDefaultsの拡張なので、このように使います。
通常
let boy = Friend.init("Tom",25) //保存 UserDefaults.standard.setFriend(boy,"friend") //取得 let friend = UserDefaults.standard.getFriend("friend")配列の場合
let boys = [Friend.init("Tom",25),Friend.init("Bob",18)] //保存 UserDefaults.standard.setFriends(boys,"friends") //取得 let friends = UserDefaults.standard.getFriends("friends")おわりに
通常のUserDefaultsのように1行で使えるので、コードが見やすくなると思います。
間違っている部分があったら教えていただけると助かります?♂️
- 投稿日:2020-05-24T09:43:36+09:00
【プログラミング初心者】Swift基礎~変数・型~
プログラミング初心者、初級者に向けてSwiftをベースとした変数宣言、型の種類について紹介していきます。
変数
プログラミングでは処理をする際様々なデータを扱います。
このデータはコンピュータの記憶領域(メモリ)に保存されます。
プログラミングではこのメモリに変数名前を付け、変数経由でメモリの読み書きをします。変数を扱う前には「今からこんな名前の変数を使います」とコンピュータに教えてあげる必要になります。
これを変数を宣言するとか定義するとか言ったりします。
以下の例はx
という名前の変数をInt型で宣言しています。(※型については後述)var integer: Int宣言した後は
integer
という名前でメモリにアクセスすることができます。
integer
という名前の箱をメモリに準備し、その中のデータを参照したり更新するというようなイメージです。変数・定数
先述した説明ではデータの扱いはまとめて変数といってますが実際には変数と定数があります。
それほど違いはなくイメージのまま変数は読み書き可能で、定数の場合書き込みは初期化の時のみでその後はデータの更新はできません。
宣言の仕方が変数と定数では少し異なり、変数の場合はvar
、定数の場合はlet
をそれぞれ宣言時に付けます。var variable: Int = 0 variable = 10 // 変数は書き換え可能 let constant: Int = 0 constant = 10 // 定数は読み込みのみなのでエラー書き換えの必要がないものには
let
を使ってあげましょう。型について
変数を扱う際は型というものを意識します。
型とは扱う変数がどんな種類でどんな性質を持っているのかなど、そのデータの特性を表すものです。
代表的なものとしては文字列を扱うString型、整数を扱うInt型などが挙げられます。変数は指定された型しか扱えません。
例えばInt型で宣言した変数に文字列を格納しようとしてもプログラムは理解できずエラーとなります。var integer: Int = 0 integer = "文字列を代入" // 型が異なるためエラー(補足ですが文字列は
"~"
で表されます)このように明確に型を付けてあげることを型アノテーションといいます。
もし型というものがなければ、例えば金額の計算をしていたのにいつの間にか「ああああ111」という結果になってしまうということもありえます。
そうなるとアプリが実装者の意図した挙動をしない可能性が出てきます。それを未然に防ぐためにSwift自体がエラーを出してくれます。余談ですがSwiftなどはコンパイラ言語と呼ばれ型宣言を必要としています。一方でスクリプト言語と呼ばれるJavaScriptやPHPなどでは型がありません。
そのためスクリプト言語ではいつの間にか変数が文字列に変わっていたということは発生したりします。これは実装者がそうならないように気をつけて実装していく必要があります。メモリアドレス
少し変数宣言について内部処理の話をします。少し難しいかもしれないので、理解できなければそんな感じなのかーくらいの認識を持つ程度で大丈夫です。
そこまで気にしなくてもSwiftはとりあえずは書けます。実際、変数にはアドレスと呼ばれるメモリの場所、住所が記録されています。
メモリはコンピュータから1bitに対し1アドレスが割り当てられています。(1byte毎かもしれません。。)
アドレスは整数で定義されており16進数で表されることが多いです。
そしてメモリ1つ1つに対して0 or 1のデータが格納されています。
変数は扱うデータを表すメモリ領域の先頭のアドレスを持ちます。
先頭アドレスがわかればどこにデータの内容が確保されているかわかります。
では先頭はわかりましたがこの変数はどのメモリまでをデータとして扱えばいいのでしょうか?
それを表すのが型です。
型にはそれぞれ必要とする長さが決まっています。
(全部がそうではありませんがそういったイメージでいいです)
環境によって変わったりしますがIntなら64bitを確保します。
このようにしてどこからどこまでを1つのデータとして扱うかを決めています。
結局扱う際はこのデータのまとまりを1データとして扱うためIntとして大きな箱がメモリに準備されると思ってもらえれば問題ありません。型推論
さて、ここまで変数には型が必要だと説明してきましたが、実はSwiftでは実装者が型アノテーションで明確に型を宣言しなくても良い場合があります。むしろそのパターンの方が多かったりします。
この機能を型推論といい、読んで字のごとく型を推測してくれます。例えばIntの場合以下のように定義することができます。
let integer = 0このように宣言した場合、Swiftの方で勝手にこれは整数を扱っているから
Int
型だなというように推測してくれ、その後はInt型として扱うことができます。型推論とはいえSwift側では型を明確にできるためIntと推論されたものに対しては文字列は代入できません。
var integer = 0 integer = "文字列を代入する" // Swift側でエラーとして扱ってくれるこのようにSwift側でエラーを表示してくれるため型の違いによる意図しない挙動は起こらないようになっています。
一方で型アノテーションをしてあげないといけない型もあります。
例えば使うことは少ないですがInt32
型などです。Int32
型はInt
型と違い32bit分の整数しか扱わない型です。
同じ整数型ですがvar integer = 0
と宣言するとSwiftはInt
と認識するため
明示的にvar integer: Int32 = 0
としてあげる必要があります。特に明確に型を区分する場合は以外は型推論を使ってあげればいいかと思います。
基本型一覧
本項ではSwiftの基本となる型を紹介します。
整数
整数データを扱う型です。
基本的にはInt
型を使います。
Int型は型アノテーションを必要としません。整数型には符号ありと符号なしがあります。符号なしは負の数「-」は扱わないという意味です。
それぞれの型で扱える値の範囲が違います
- 符号あり
- Int,Int8,Int16,Int32,Int64
- 符号なし
- UInt,UInt8,UInt16,UInt32,UInt64
let integer: Int = 10 // 32bit環境ではInt32、64bit環境ではInt64と同じ let integer8: Int8 = 10 // -128〜127 let integer16: Int16 = 10 // -32768〜32767 let integer32: Int32 = 10 // -2147483648〜2147483647 let integer64: Int64 = 10 // -9223372036854775808〜9223372036854775807 let uInteger: UInt = 10 // 32bit環境ではUInt32、64bit環境ではUInt64と同じ let uInteger8: UInt8 = 10 // 0〜255 let uInteger16: UInt16 = 10 // 0〜65535 let uInteger32: UInt32 = 10 // 0〜4294967295 let uInteger64: UInt64 = 10 // 0〜18446744073709551615少数
主に
Double
型とFloat
型があります。
それぞれ内部での数値の持ち方が異なり、Doubleの方が正確です。
基本的にはDouble
を使用します。
型アノテーションなしの場合はDouble
型になります。let double: Double = 0.1 // 64bit let float: Float = 0.1 // 32bit let float32: Float32 = 0.1 // 32bit let float64: Float64 = 0.1 // 64bit let cgFloat: CGFloat = 0.1 // 32bit環境ではFloat32、64bit環境ではFloat64と同じ。UI周りでよく使われている文字列
String
型のみと思っていいです。
型アノテーションなしで使用できます。let string: String = "文字列"論理型
Bool
型。真偽値を扱います。
値はtrue/falseのみです。
型アノテーションなしで使用できます。主に条件判定の際に使います。
var boolean: Bool = true var equalBoolean = 0 == 0配列
「配列」とは複数の値を格納するための型です。
画像を見たほうが想像が付きやすいと思います。
少し注意が必要なのは配列の最初が0番目から始まることです。
数学の数列などでは1番目から扱うことが多いですがそこは異なっています。配列は
Array
型ですが、宣言の仕方が少し特殊です。配列には変数を格納していくため、その変数自体の型情報も一緒に定義してあげる必要があります。
宣言した型以外のものを入れるとSwiftからエラーが表示されます。
宣言の構文はvar 名前: [型] = 初期値
です。var integerArray: [Int] = [0, 1, 5, 19] // Intを格納する配列 var stringArray: [String] = ["りんご", "みかん", "バナナ"] // Stringを格納する配列
配列名[n]
で格納した値にアクセスすることができます。// 参照 print(integerArray[0]) // 0 print(stringArray[2]) // バナナ // 更新 stringArray[0] = "パイナップル" print(stringArray[0]) // パイナップルまた初期値は空でもよく、さらに変数として宣言している場合後から追加、削除できます。
追加は配列名.append(追加する要素)
削除は配列名.remove(at: 削除する要素の配列番号)
※append
は配列の一番後ろに要素を追加します。他にも間に挿入するなどもあります。// 配列の初期値を空で宣言 var stringArray: [String] = [] // 追加処理 stringArray.append("りんご") stringArray.append("みかん") print(stringArray) // ["りんご", "みかん"] // 削除処理 stringArray.remove(at: 0) print(stringArray) // ["みかん"]補足として配列を定数として宣言した場合、更新はできません。
append
やremove
も同様に使えません。let constantArray: [String] = ["りんご", "みかん"] // 更新できない constantArray[0] = "パイナップル" //エラー constantArray.append("メロン") // エラー constantArray.remove(at: 0) // エラー // 参照はできる print(constantArray) // ["りんご", "みかん"] print(constantArray[0]) // "りんご"また、配列は初期値が空でない場合は型アノテーションなしで宣言することができます。
var integerArray = [0] // Int型の配列として定義される var stringArray = ["りんご"] // String型の配列として定義される var unknownArray = [] // 型が推測できないためエラー基本的な情報としてはこれくらいですが、Swiftの配列操作は色々な機能があるので別途まとめます。
辞書
配列と同様に複数の値を格納するための型です。配列を拡張したようなものです。
配列は格納した要素を配列番号(インデックス)で管理しますが、辞書ではキーで管理します。
キーとは検索ワードのようなものをイメージしてもらえるとわかりやすいかと思います。
辞書はkey-valueの形でキーと値をワンセットとして保持します。
辞書は
Dictionary
型ですが配列と同様に値の型、さらにキーの型を一緒に宣言してあげる必要があります。
辞書も当然宣言した型以外のものを入れるとSwiftからエラーが表示されます。
宣言の仕方はvar 名前: [キーの型: 値の型] = 初期値
となります。// キーがString, 値がString var stringDictionary: [String: String] = ["apple": "りんご", "orange": "オレンジ", "banana": "バナナ"] // キーがString, 値がInt var intDictionary: [String: Int] = ["apple": 1, "orange": 1, "banana": 5] // キーがInt, 値がString var keyIntDictionary: [Int: String] = [1: "りんご", 3: "オレンジ", 5: "バナナ"]初期化の際注意が必要なのはキーが重複しないようにしてください。
同じキーを定義してもSwiftからエラーが返ってきませんが、初期化されるタイミングでアプリがクラッシュするようです。
値は同じ値があっても問題ありません。値へのアクセスは
辞書名[キー名]
です。// 参照 print(stringDictionary["apple"]) // りんご print(intDictionary["banana"]) // 5 print(keyIntDictionary[3]) // オレンジ // 更新 stringArray["apple"] = "アップル" print(stringArray["apple"]) // アップルまた当然初期値は空でもよく、変数で宣言した場合は追加/削除することができます。
追加は更新と同じ方法で辞書名[キー名] = 値
です。格納先のキーが存在しない場合は新しい要素として追加されます。
削除は辞書名.removeValue(forKey: キー名)
// 配列の初期値を空で宣言 var dictionary: [String: String] = [:] // 追加処理 dictionary["apple"] = "りんご" dictionary["orange"] = "オレンジ" print(dictionary) // ["orange": "オレンジ", "apple": "りんご"] // 削除処理 dictionary.removeValue(forKey: "apple") print(dictionary) // ["orange": "オレンジ"]※意識することは少ないかもしれませんが配列と違い、辞書型では登録した順番は担保されずどのような順番で保持されているかはわかりません。
型宣言についても配列と同様、空以外の初期値を与えてあげると型推論することができます。
このように辞書型は配列と似たような性質であることがわかります。
配列はインデックスという順番で管理し、辞書はキーという実装者が見て意味がわかりやすいもので管理すると覚えておいてください。余談ですが辞書型は別の言語では連想配列と呼ばれたりもします。
最後に
変数、型、基本型の種類にについては以上です。
これくらいあればある程度表現できるではないでしょうか?本記事とは別でプログラミング未経験からiOSアプリ開発が行えるようになることを目的とした記事を連載しています。
連載は以下にまとめていますのでそちらも是非もご覧ください。
アジェンダ:https://qiita.com/euJcIKfcqwnzDui/items/0b480e96166e88945684
- 投稿日:2020-05-24T07:37:12+09:00
Swift プログラムのデバッグテクニック - ブレイクポイント、ランタイム変数検査
この記事では、一般的なバグ修正方法をいくつかご紹介します。
一般的なデバッグ手法
print, print, print...
Printステートメントは常に役に立ちます。それにより、プログラムのランタイム中に変数を出力できます。
func dailyRoutine(){ print(myCats) myCats.forEach { (cat) in print("猫の名前は\(cat.name)です。") cat.feed() cat.hug() } }ブレークポイント
行番号をクリックしてブレークポイントを
Xcode
に設定できます。プログラムは、シミュレーターか接続したiOSデバイス上で実行しているとき、ブレークポイントに到達すると停止します。ブレークポイントには以下のようないくつもの種類があります。通常のブレークポイント
通常のブレークポイントを作成するには、
Xcode
のコード行の行番号をクリックします:プログラムが停止すると以下のようになります:
条件付きブレークポイント
条件付きブレークポイントは、ある特定の条件を満たした場合にのみコードを停止させます。例えば、ネコの名前が "ネコノヒー" : の場合にのみ、プログラムの実行を停止させたいならば
- 通常のブレークポイントの作成
- 青ラベルをダブルクリックする
- 条件フィールドに条件を入力する。ここでは
cat.name == "ネコノヒー"
と入力しました。表示変数
ブレークポイントに来たらアクションを実行して、プログラムを停止しないこともできます。
print(cat.name)上記コードは次のように置き替えることもできます。
- 通常のブレークポイントを作る
Add Action
ボタンをクリックするpo cat.name
などのアクションを入力する。po
がどのようなものであるかについては、次のパートで取り上げます。- トグルスイッチを、評価アクション後に自動的に継続するオプションに合わせる
Automatically continue after evaluating actions
ブレークポイントに来てもプログラムは停止せず、猫の名前がコンソールに表示されます。
Xcodeが組み込まれたランタイムデバッガ (po)
ブレークポイントでプログラムが停止した時は、変数の値を調べて、変数値を変えて下さい。
例えばここにブレークポイントを置くことができます。
ブレークポイントでプログラムが停止した時は、コンソールウィンドウで命令を実行できます。
変数を調べることができます。
ここでは、
po
は表示オブジェクトにあたります。
po myCats
po myCats.count
po myCats.first?.name
変数を変更することも可能です
po myCats.count //2 po myCats.append(.init()) po myCats.count //3Xcode オーガナイザー
App Storeで既にプログラムが公開されていれば、Appleがクラッシュの情報を集めてくれます。
Xcode
のトップメニューで、"Window"と"Organizer"をクリックしてください。左側でApp Storeで既に公開しているアプリケーションをクリックして、トップの"Crashes"をクリックしてください。
App Storeにある私のアプリで起きた実際のクラッシュに関するレポートをシェアしたいと思います。この問題はすでに解決済みです。
まずはバージョンのドロップダウンメニューをクリックしてバージョンを選択してください。
実際に起きたソフトウェアのクラッシュのリストが表示されるかと思います。そのうちの1つをクリックして詳細を見てください。
青色の "Open in Project" (プロジェクト内で開く) ボタンをクリックすると、自分のプロジェクトファイルを選択でき、どのラインでクラッシュがあったのかを教えてくれます。
Metrics
これまでの記事で話したように、メトリクスをチェックしてメモリに問題がないかどうかを確認することもお勧めします。
iOSアプリのメモリリーク (Memory Leak) の検出とデバッグの方法を、ネコに関するプログラムで学びましょう。(Reference Cycle 参照サイクル)
- 投稿日:2020-05-24T05:56:22+09:00
【Swift】位置情報を取得する
概要
swiftで位置情報を取得する方法について、まとめました。
startUpdatingLocation() で位置情報の取得を開始し、取得した場合 func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) が呼び出され位置情報を確認することだできます。
class ViewController: UIViewController, CLLocationManagerDelegate { var locationManager : CLLocationManager? override func viewDidLoad() { super.viewDidLoad() locationManager = CLLocationManager() locationManager!.delegate = self //位置情報を使用可能か if CLLocationManager.locationServicesEnabled() { //位置情報の取得開始 locationManager!.startUpdatingLocation() } } // 位置情報を取得した場合 func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { } }info.listに追記
位置情報を取得する場合、info.list に「Location Always and When In Use Usage Description」、「Location When In Use Usage Description」を追加し位置情報どのように使用するか記載する必要があります。
info.list// ユーザーの位置情報へのアクセスを常に要求している理由を記載します。 Privacy - Location Always and When In Use Usage Description // フォアグラウンドで実行されているときに、アプリがユーザーの位置情報へのアクセスを要求する理由を記載します。 Privacy - Location When In Use Usage Description位置情報を取得する際の設定
位置情報を取得する際に、精度や取得間隔の指定ができます。
locationManager = CLLocationManager() locationManager!.delegate = self // 常に使用する場合、バックグラウンドでも取得するようにする if CLLocationManager.authorizationStatus() == .authorizedAlways { // バックグラウンドでも取得する locationManager!.allowsBackgroundLocationUpdates = true } else { // バックグラウンドでは取得しない locationManager!.allowsBackgroundLocationUpdates = false } // 位置情報の取得精度を指定します locationManager!.desiredAccuracy = kCLLocationAccuracyBest // 更新に必要な最小移動距離 // Int値を指定することで、○○m間隔で取得するようになります locationManager!.distanceFilter = 10 // 移動手段を指定します // 徒歩、自動車等 locationManager!.activityType = .fitness // 位置情報取得開始 locationManager!.startUpdatingHeading()CLLocationManager.authorizationStatusの設定値
authorizationStatus 設定 .notDetermined 未設定 .restricted 機能が制限されている .denied 許可しない .authorizedWhenInUse このAppの使用中のみ許可 .authorizedAlways 常に許可 activityTypeの設置値
activityType 移動手段 .fitness 徒歩 .automotiveNavigation 自動車 .other その他 desiredAccuracyの設置値
desiredAccuracy 精度 kCLLocationAccuracyBestForNavigation デフォルト kCLLocationAccuracyBest 最高精度 kCLLocationAccuracyNearestTenMeters 10m以内 kCLLocationAccuracyHundredMeters 100m以内 kCLLocationAccuracyKilometer 1km以内 kCLLocationAccuracyThreeKilometers 3km以内 位置情報を取得した場合
位置情報を取得した場合以下の、関数を呼び出し緯度経度等の情報を取得できます。
// 位置情報取得した場合 func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { guard let newLocation = locations.last else { return } let location:CLLocationCoordinate2D = CLLocationCoordinate2DMake(newLocation.coordinate.latitude, newLocation.coordinate.longitude) let formatter: DateFormatter = DateFormatter() formatter.timeZone = TimeZone(identifier: "Asia/Tokyo") formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss'Z'" let date = formatter.string(from: newLocation.timestamp) print("緯度:", location.latitude, "経度:", location.longitude, "時間:", date) }参考
- 投稿日:2020-05-24T02:57:41+09:00
【SwiftUI】ビューをジェスチャーで回転させ、90°ずつスナップを効かせる方法
【SwiftUI】ビューをジェスチャーで回転させ、90°ずつスナップを効かせる方法
こんにちは、たいちです(@taichi_swift_)
たかし「お絵かきアプリとかでよくある、ビューを回転させて90°づつピタって止めるやつ、どうやって実装するんだろ...」
こちらのお悩みを解決します。
ビューを回転させる方法
まずは、基本のビューを回転させる方法です。
SwiftUIでは、
.rotationEffect(_ angle: Angle, anchor: UnitPoint = .center)
というmodifierを使用します。実際に使うときには、
.rotationEffect(Angle(degrees: 90))
みたいに使うわけですね。Text("RotationEffect") .rotationEffect(Angle(degrees: 90))ビューをジェスチャーで回転させる方法
続いて、ユーザーが指を使ってビューを回転させる方法です。
.gesture()
を使います。まずは、角度を格納する変数を2つ用意します。
@State var angle = Angle(degrees: 0.0) @State private var oldAngle = Angle(degrees: 0.0)そして、ジェスチャーを用意します
var rotation: some Gesture { RotationGesture() .onChanged { angle in self.angle = self.oldAngle + angle } .onEnded { angle in self.oldAngle = self.angle } }そんでこのジェスチャーをテキトーなビューにがっちゃんこします。
.rotationEffect(self.angle)
.gesture(rotation)
RoundedRectangle(cornerRadius: 20) .frame(width: 400, height: 300) .rotationEffect(self.angle) .gesture(rotation)ビューを回転させ、スナップさせる方法
やっときました。本題です。
先ほど定義した、rotationという名前のgestureにちょこっと細工を加えてあげます。
var rotation: some Gesture { RotationGesture() .onChanged { angle in self.angle = self.oldAngle + angle if abs(Int(self.angle.degrees) % 90) < 5 { self.angle = Angle(degrees: Double(Int(self.angle.degrees) - Int(self.angle.degrees) % 90)) } } .onEnded { angle in self.oldAngle = self.angle } }完成です。
struct ContentView: View { @State var angle = Angle(degrees: 0.0) @State private var oldAngle = Angle(degrees: 0.0) var rotation: some Gesture { RotationGesture() .onChanged { angle in self.angle = self.oldAngle + angle if abs(Int(self.angle.degrees) % 90) < 5 { self.angle = Angle(degrees: Double(Int(self.angle.degrees) - Int(self.angle.degrees) % 90)) } } .onEnded { angle in self.oldAngle = self.angle } } var body: some View { RoundedRectangle(cornerRadius: 20) .frame(width: 400, height: 300) .rotationEffect(self.angle) .gesture(rotation) } }
- 投稿日:2020-05-24T00:04:01+09:00
【SwiftUI】基本的なモディファイアの種類と使い方②
テキストの装飾に関するモディファイア
lineLimitモディファイア
文字列の表示行数の最大値を設定
例:文字列"hogehoge"を20回繰り返して3行に収めるText(String(repeating:"hogehoge", count:20)) .lineLimit(3)truncationModeモディファイア
文字列を省略する場所を指定
例:文字列の中心が省略Text(String(repeating:"hogehoge", count:20)) .lineLimit(1) .truncationMode(.middle)lineSpacingモディファイア
行間の幅を指定
cssのline-heightと同じ
例Text("hogehoge") .lineSpacing(50)fontモディファイア
フォントの種類を指定
種類:
largeTitle:大きなタイトル
title:タイトル
headline:見出し
subheadline:小見出し
body:本文
callout:吹き出し
footnote:注釈
caption:キャプション
例Text("hoge") .font(.body)boldモディファイア
文字を太字にする
italicモディファイア
文字を斜体にする
fontWeightモディファイア
文字の太さを設定する
(太い順にblack,heavy,bold,semibole,medium,regular,light,thin,ultraLight)baseLineOffsetモディファイア
ベースラインの太さを設定
正の数字ベースラインが上がるkerningモディファイア
2文字間の間隔を設定
trackingモディファイア
文字列全体の文字間隔を調整
kerningより優先されるunderlineモディファイア
文字列にアンダーラインを表示
active:下線を表示するかどうか
color:下線の色strikethroughモディファイア
文字列に取り消し線を表示
active:取り消し線を表示するかどうか
color:取り消し線の色