- 投稿日:2021-01-05T23:30:06+09:00
[Swift] 遷移先で変更した配列を遷移元に反映させ、Label等に表示させる方法
はじめに
今回はNavigationControllerを用いて、画面遷移を行っています。そのため、遷移先から遷移元に戻る時に呼ばれるnavigationControllerのwillShowを使っています。また、collectionViewのCell内にLabelを置き、そこに配列(String)の値を表示させています。
開発環境
macOS 11.0.1 Xcode version 12.2 Swift version 5.3.1コード
親画面にはString型で変数aを与え、初期値として5個の何の値も入っていない値を与えておきます。これを、collectionViewを用いて、Cell内に設置したLabelに先程の値を与えています。遷移先で変更された配列を遷移元に渡した時にプロパティ・オブザーバを設定してあげることで変更が起こった時の処理を書くことができるようになります。オブザーバは、プロパティ変更前に呼ばれる willSet と、変更後に呼ばれる didSet があります。今回は、変更後に呼ばれるdidsetを使います。
FirstViewContorollerimport UIKit class ViewController: //collectionViewを用いるため、下の2つを書いておく UIViewController,UICollectionViewDelegate,UICollectionViewDataSource { //上と同じ理由で書いておく @IBOutlet weak var collectionView: UICollectionView! var a = ["","","","",""] {didSet{ //didset内に書くことで、変数aに変更が起こった場合にreloadDataし、label内の表示 される値も変更することができる collectionView.reloadData() } } override func viewDidLoad() { super.viewDidLoad() } func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return a.count } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for:indexPath) cell.backgroundColor = .lightGray //タグつけしたlabelに変数aの値を順に表示 var aLabel = cell.contentView.viewWithTag(1) as! UILabel aLabel.text = a[indexPath.row] return cell } //ほかには、何かしらの方法で遷移するコード書く }NextViewControllerimport UIKit class NextViewController: UIViewController,UINavigationControllerDelegate { @IBOutlet weak var textFiled: UITextField! override func viewDidLoad() { super.viewDidLoad() } //ナビゲーションバーの戻るボタンを押した時に呼ばれる処理。下記を書くことで遷移先で遷移元の値 をいじることができる func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) { //親画面のファイル名を記入 if let controller = viewController as? FirstViewContoroller{ //今回は変数aの1番目の値を変更する //遷移先画面のテキストフィールドに打たれた文字が遷移元の変数aの1番目の値を変更する controller.a[0] = textfiled.text! }まとめ
今回のコードで重要なことは、プロパティ・オブザーバの一つであるdidsetです。この中でcollectionView.reloadData()を書くことで、変数である配列に変更が起きた場合に、
collectionView.reloadData()が呼ばれ、cell内のlabelに表示される値を変更毎に更新することができます。
- 投稿日:2021-01-05T20:10:52+09:00
Swiftでドキュメントベースアプリを作る時はクラス名の設定に注意が必要
当初シングルのアプリとして作っていたものに、後からドキュメントベース要素を追加しようとした時にどうしてもファイルを認識してくれなくてハマったことのメモ。
ドキュメントベースアプリではInfo.plistの項目としてDocument Typeの設定を行う箇所がある。この場所でファイルのUTIやRoleを宣言する他、Documentクラスと連携する場合はClassに対応するCocoaのクラスを設定する。
Objective-Cの場合はクラス名をそのまま書けば問題ないのだが、Swiftでモジュールを構成する場合、Documentクラスの名称は次のように書くのが正式となる。Info.plist$(PRODUCT_MODULE_NAME).DocumentClassこれが正しく設定されていないとオープンダイアログやDockのドラッグでファイルを渡しても認識してくれない。
エラーメッセージも開けないということしか教えてくれず、なぜ開けないのか説明がないのでハマってしまう。実はAppleのサンプルに記述があるのだが、NSDocumentには直接記載がない上、ドキュメントベース周りは古いドキュメントも多いので、うっかりObjective-Cの感覚でいると見落としてしまうと思う。というか、これで一時間悩んだ。
- 投稿日:2021-01-05T18:41:20+09:00
【Swift】SwityJSONを使用して占いAPIのJSONデータから簡単に情報を取得する
占いを無料で提供しているAPIがあったので、Swiftで呼び出そうと思ったらJSONの子要素の名前が日付(2021/01/01)のような形式で戸惑ってしまった。
SwiftyJSONを使用することで解決出来そうなので試してみた。開発環境
Xcode 12.3
SwiftyJSON 5.0.0SwiftyJSONを使用し、APIから情報を取得
例として、2021/01/05の牡羊座の恋愛運(love)を取得
import UIKit import SwiftyJSON class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() getHoroscopeDate() } private func getHoroscopeDate() { guard let url = URL(string: "http://api.jugemkey.jp/api/horoscope/free/2021/01/03") else { return } let task = URLSession.shared.dataTask(with: url) { (data, response, error) in if let error = error { print("error", error) return } guard let data = data else { print("Data情報の取得に失敗しました") return } do { let json = try? JSON(data: data) if let json = json { let love = json["horoscope"]["2021/01/05"][0]["love"] print(love) } } } task.resume() } }API用に構造体を作成する必要もなく、かなり簡単に希望の情報が取得出来ました。
JSONデータ
取得結果(2021/01/05)
{ "horoscope": { "2021/01/05": [ { "content": "図書館で過ごす時間は、あなたの想像力を高めてくれます。悩んでいることがあるなら、好きな作家の本を読んでみて。", "item": "鍋料理", "money": 4, "total": 3, "job": 3, "color": "グリーン", "day": 5, "love": 3, "rank": 7, "sign": "牡羊座" }, .......以下11星座分が続きます ] } }参考
使用したAPI
Web ad Fortune 無料API
利用の条件があり、商用利用する場合は有料版を使うようにしてください。まとめ
SwiftyJSONを使用したら本当に簡単にJSONデータから情報を取得できた。
とても便利なライブラリなので理解をもっと深めたい。
- 投稿日:2021-01-05T18:29:29+09:00
Alamofireでインターネット接続状態を確認する
はじめに
AlamofireでAPIリクエストをする際に端末がインターネット接続されていなかった場合エラーを流す必要がありました。
その時に調べたことのまとめになります。NetworkReachabilityManagerを使う
Alamofireには標準で
NetworkReachabilityManager
というクラスがあります。
今回はそちらを使用して接続状態を確認します。
参照使い方
その①
ベタがき
コード量が少ないがパッと見読みにくいimport Alamofire if let isConnected = NetworkReachabilityManager()?.isReachable, !isConnected { print("Disconnect") }その②
クラスを定義する
見やすいimport Alamofire class ConnectCheck { func isConnectedNetwork() -> Bool { return NetworkReachabilityManager()?.isReachable ?? false } }使う時
if ConnectCheck.isConnectedNetwork { print("Connect") } else { print("Disconnect") }こんな感じでした。
- 投稿日:2021-01-05T15:32:09+09:00
TextField付きアラート
はじめに
ちょっと何かを入力させる時に、いちいちUI配置したりしなくて済むので便利で使える。
環境
Xcode 12.3
コード
let title = "Title" let msg = "Message" let alert = UIAlertController(title: title, message: msg, preferredStyle: .alert) //OKボタンの設定 let okAction = UIAlertAction(title: "OK", style: .default, handler: {(action:UIAlertAction!) -> Void in //テキストフィールド付きアラートでOKボタンが押された時の処理 //テキストフィールドに入力された値をオプショナルバインディングして取り出す if let str = alert.textFields?[0].text{ print(str) } }) alert.addAction(okAction) //キャンセルボタンの設定 let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) alert.addAction(cancelAction) //テキストフィールドの追加 alert.addTextField(configurationHandler: {(textField:UITextField!) -> Void in // ここでテキストフィールドのカスタマイズができる textField.placeholder = "" textField.keyboardType = .default }) // 複数追加したいならその数だけ書く // alert.addTextField(configurationHandler: {(textField: UITextField!) -> Void in // textField.placeholder = "テキスト" // }) //アラートを画面に表示 self.present(alert, animated: true, completion: nil) }textFieldを複数使用したい時はaddTextFieldを複数書く
- 投稿日:2021-01-05T14:39:09+09:00
文字列(リソース名)で画像を取得してImageViewに貼り付ける(kotiln)
以前書いた記事
画像名を文字列で指定してImageViewに貼り付けるコード(android)これにLGTMしていただいていたので見てみたのですが、微妙だったので書き直します(ついでにkotlinで)
リソース名からImageViewに画像をセットするメソッドは、swiftにはあるんですがkotlinだとありそうでないです。ちなみにswiftでは
imageView.image = UIImage(named:"hoge.png")こんな感じでimageViewに画像をセットできます。
同じようなものをkotlinで再現してみます。
resourceIdを文字列から取得
StringExtensions.ktfun String.getResourceId(context: Context): Int { return context.resources.getIdentifier(this, "drawable", context.packageName) }Stringの拡張クラスを定義しました
画像をImageViewにセット
ImageViewExtensions.ktfun ImageView.setImageResourceByName(name: String) { this.setImageResource(name.getResourceId(context)) }ImageViewクラスの拡張を定義しました。
使用例
private fun setImage() { val imageView = ImageView(this) imageView.setImageResourceByName("some_image_name_string") parent_view.addView(imageView) }parent_viewに動的にImageViewを追加しています
kotlinっぽい書き方をすると
private fun setImage() { parent_view.addView( ImageView(this).apply { setImageResourceByName("some_image_name_string") } ) }こんな感じでしょうか。
最初は見にくいなと思っていたのですが、「parent_viewに画像を追加する」という責務がひとかたまりで表せるので個人的には気に入っています。swiftとの比較
swiftimageView.image = UIImage(named:"some_image_name_string.png")kotlinimageView.setImageResourceByName("some_image_name_string")大体似たような感じになったかと思います。
上記で定義したStringとImageViewの二つの拡張をコピペすればそのまま使えます。
- 投稿日:2021-01-05T14:29:02+09:00
UserDefaultsに保存されているデータを全て表示する
- 投稿日:2021-01-05T14:04:44+09:00
Swift extension集
はじめに
何気なく調べたりして使っていると、また使おうとした時にどう書くんだっけな・・・
ということがよくあるので忘備録として記録します。ダークモードかどうかをBoolで返す
extension
extension UITraitCollection { public static var isDarkMode: Bool { if #available(iOS 13, *), current.userInterfaceStyle == .dark { return true } return false } }使い方
if UITraitCollection.isDarkMode { //trueの場合 }else{ //falseの場合 }UIColorをRGB(0~255)で指定する
extension
extension UIColor { static func rgb(red: CGFloat,green: CGFloat,blue: CGFloat,alpha: CGFloat) -> UIColor { return self.init(red: red / 255, green: green / 255, blue: blue / 255,alpha: alpha) } }使い方
let color:UIColor = .rgb(red: 10, green: 10, blue: 10, alpha: 1.0)
- 投稿日:2021-01-05T13:50:28+09:00
INRestaurantReservation: ユーザーの予約情報をSiriに共有して、イベントを自動的に提案する
あなたがレストラン予約アプリケーション、またはユーザーのために予約を入れるアプリケーション(フライト、列車、チケット)を開発している場合、予約の詳細についてSiriに通知することができます。
この記事では、予約アプリのデモを作成します。ユーザーが予約を行うと、アプリが予約情報をSiriに送信し、Siriは予約情報をカレンダーに追加するようユーザーに促します。予約が閲覧されると、アプリが予約時間を更新します。システムカレンダーアプリが予約情報の更新について、ユーザーに通知するのが確認できます。
利点
Siriに通知することで、ユーザーはイベントをカレンダーに追加するための通知を即座に受け取ります。
イベント会場の詳細情報もSiriによって自動的に入力されます。
また、Siriは予約前に関連情報をユーザーの画面に表示します。
もう一つのメリットは、(予約IDに基づいて)特定の予約イベントに関する最新情報がアプリ上で更新されると、Siriがイベントの詳細を自動的に更新することです。アプリ上で既存の予約に変更があると、システムカレンダーアプリに通知バッジが表示されます。
import intents
Github: https://github.com/mszmagic/SiriReservationSample
ステップ 1. 期間を定める
まず、予約が続行する期間を定める関数を書きます。実演アプリケーションでは、レストランの予約を2時間に設定しました。
private func getReservationPromoteDateRange(_ item: Reservation) -> INDateComponentsRange { let calendar = Calendar.autoupdatingCurrent // 予約時間の2時間後まで、予約の詳細を閲覧するようユーザーに宣伝しましょう let promoteDate_end = calendar.date(byAdding: .hour, value: 2, to: item.bookedTime) ?? item.bookedTime let promoteDate_end_components = calendar.dateComponents(in: TimeZone.autoupdatingCurrent, from: promoteDate_end) let promoteDate_start = item.bookedTime let promoteDate_start_components = calendar.dateComponents(in: TimeZone.autoupdatingCurrent, from: promoteDate_start) // return .init(start: promoteDate_start_components, end: promoteDate_end_components) }ステップ 2. 予約名を定める
予約項目に対して固有の識別名を作成する必要があります。
let reservation_item = INSpeakableString(vocabularyIdentifier: item.reservationID, spokenPhrase: "\(item.restaurantName)の予約", pronunciationHint: nil)ステップ 3. 予約を見る時に実行するアクションを定める
それから、ユーザーが予約を見たい時に実行するアクションを定めます。URLを提供することもできます。これらの情報は、ユーザーが特定の予約項目を見たい時に、どのようなアクションを実行するかをSiriが認識するために使用されます:
private func getViewReservationDetailsAction(_ item: Reservation) -> INReservationAction { let viewDetailsAction = NSUserActivity(activityType: "com.example.SiriReservationSample.viewReservationDetails") let reservationDateStr = DateFormatter.localizedString(from: item.bookedTime, dateStyle: .short, timeStyle: .short) viewDetailsAction.title = "\(item.restaurantName)における\(reservationDateStr)での予約状況の詳細を表示します" viewDetailsAction.userInfo = ["reservationID" : item.reservationID] viewDetailsAction.requiredUserInfoKeys = ["reservationID"] viewDetailsAction.webpageURL = generateReservationURL(item) return .init(type: .checkIn, validDuration: getReservationPromoteDateRange(item), userActivity: viewDetailsAction) }ステップ4. 予約オブジェクトを決めてください
let reservation = INRestaurantReservation(itemReference: reservation_item, reservationNumber: item.reservationID, bookingTime: item.bookedTime, reservationStatus: item.reservation_status, reservationHolderName: item.personName, actions: reservation_actions, url: generateReservationURL(item), reservationDuration: getReservationPromoteDateRange(item), partySize: item.reservation_partySize, restaurantLocation: item.restruantLocation)
Variable name Explanation itemReference 参照IDオブジェクト reservation_item
reservationNumber 予約ID bookingTime ユーザーがこの予約を入れる時間 reservationStatus 予約状況 reservationHolderName この予約を行なった人物の名前 actions この例における関数 getViewReservationDetailsAction()
のアウトプットurl ユーザーが予約詳細へアクセスするためのURLアドレス(ウェブページ) reservationDuration 予約の開始と終了を定めてください。 getReservationPromoteDateRange()
機能の出力partySize この予約の対象人数 restaurantLocation レストランの所在地 CLPlacemark
ステップ5.予約についてシステムに入力してください。
ここで、予約についてシステムに入力する必要があります。
let intent = INGetReservationDetailsIntent(reservationContainerReference: reservation_item, reservationItemReferences: nil) let response = INGetReservationDetailsIntentResponse(code: .success, userActivity: nil) response.reservations = [reservation] let interaction = INInteraction(intent: intent, response: response) interaction.donate(completion: completionHandler)これで、カレンダーのイベントに追加するためのSiriのメッセージがユーザーに表示されます。
関数を呼び出すタイミング
ユーザーが予約を閲覧するたびに(情報が変更されていなくても)上記の関数を呼び出す必要があります。予約の詳細が変更された場合は、Siriがカレンダーアプリを通じてユーザーに通知します。
なお、1つの予約に対してご自分の予約IDが同じであることを確認することが重要です。
ステップ6. NSUserActivityTypes
プロジェクトの設定から
Info
タブに移動し、サポートされているアクティビティタイプの情報を追加します。上のコードでは、アクティビティタイプとしてcom.example.SiriReservationSample.viewReservationDetails
を使用しています。ステップ 7. Siriでのアプリケーションの起動を処理
ユーザーが予約の閲覧を希望する場合、Siriでアプリケーションを起動する事ができます。
SceneDelegate.swift
ファイル内に次の関数を追加してください:func scene(_ scene: UIScene, continue userActivity: NSUserActivity) { if userActivity.activityType == "INGetReservationDetailsIntent" { // } }こちらが予約IDをフェッチする方法です:
if let userActivity = notificationObject.object as? NSUserActivity, let intentObject = userActivity.interaction?.intent as? INGetReservationDetailsIntent, let reservationName = intentObject.reservationItemReferences?.first, let reservationID = reservationName.vocabularyIdentifier { DispatchQueue.main.async { self.viewingReservationID = .init(reservationID: reservationID) } }
userInfo
プロパティの利用を試みましたが、そこには、予約IDは含まれていないようです。つきましては、代わりに上記のコードを使って予約IDを取得してください以前のコードの設定:
let reservation_item = INSpeakableString(vocabularyIdentifier: item.reservationID, spokenPhrase: "\(item.restaurantName)の予約", pronunciationHint: nil)従って、変数
vocabularyIdentifier
にアクセスすると、item.reservationID
が得られます。
- 投稿日:2021-01-05T07:09:14+09:00
[Swift5]特定の時間を比較して前なのか後なのかを判定する方法
特定の時間の比較
例えばアプリを起動したときに、現在時刻と開発者側で予め設定している時刻を比較して、その状況にあった処理をおこないたい場合があったとします。(何時より前なら処理A, 後ならBなど)
そういった状況で使えるのが
.compare( )メソッド
です。
今回はこちらのメソッドの紹介をします!記述方法
if '現在時刻'.compare('こちらで設定した時刻') == .orderedDescending { //ここで処理 //.orderedDescendingは〜より後 } if '現在時刻'.compare('こちらで設定した時刻') == .orderedAscending { //ここで処理 //.orderedAscendingは〜より前 }このように記述すればOK!
ふたつの時刻を.compare( )メソッド
で比較して、以降なのか以前なのかを判定しております。
また、双方の時刻が同じかどうかを判定したい場合は.orderedSame
を使えばいい。参考にしてください!
- 投稿日:2021-01-05T00:35:50+09:00
パスワードを入力する時に使用するTextFieldについて
問題
アカウント作成画面を作成していた時に、パスワード入力のViewにTextFieldを使用していたのだが、よく見るパスワード入力は入力文字が見えないようになっている。
SwiftUIを使用している場合、このようなTextFieldはどうやって実装するか分からなかったので調べた。解決方法
TextFieldにオプションをつけるのかと思っていたが違うらしい。
SecureFieldというViewを使用すれば良いみたい。https://developer.apple.com/documentation/swiftui/securefield
- 投稿日:2021-01-05T00:27:35+09:00
非同期処理中にユーザー操作を無効にする方法
問題
アカウント作成という非同期処理があるのだが、作成中にはユーザーの操作を無効化したい。
ただ、調べてもユーザー操作を無効にする方法はないっぽい。
そこで、ZStackを使用して、上にViewを重ねることで解決できないか検討した。解決方法
以下のコードのように、isBusyがtrueの時にのみ、Color.whiteを重ねてみたところ、下のViewを操作できないことを確認できたので、これで良さそう。
もし、より良い方法があればコメント頂けると助かります・・・。ZStack{ VStack{ TextField("ユーザー名", text: $vm.userName) .autocapitalization(.none) .textFieldStyle(RoundedBorderTextFieldStyle()) TextField("メールアドレス", text: $vm.emailAddress) .autocapitalization(.none) .textFieldStyle(RoundedBorderTextFieldStyle()) SecureField("パスワード", text: $vm.password) .autocapitalization(.none) .textFieldStyle(RoundedBorderTextFieldStyle()) SecureField("パスワード確認", text: $vm.passwordConfirm) .autocapitalization(.none) .textFieldStyle(RoundedBorderTextFieldStyle()) if vm.validationText != "" { Text(vm.validationText) .foregroundColor(.red) .font(.footnote) } Button( action: { vm.createAccount() } ){ Text("アカウント作成") .padding(4) .frame(maxWidth: .infinity) .foregroundColor(Color.white) .background(Color.gray) .cornerRadius(8) } } .padding(.horizontal) if vm.isBusy { Color.white .opacity(0.7) .edgesIgnoringSafeArea(.all) .overlay( ProgressView("アカウント作成中...") .foregroundColor(.black) ) } }
- 投稿日:2021-01-05T00:05:39+09:00
TextFieldにてアルファベット入力時に先頭文字が大文字になってしまうことを防ぐ
問題
TextFieldを用いた画面を作成した際に入力時に先頭文字が自動的に大文字になってしまうことがある。
特に、Canvasを使用して画面の動作を確認する際に、キーボードからの入力だと、大文字を小文字に変換する術がなく、辛い。
解決方法
TextFieldのModifiersである、autocapitalization(_:)を使用して、大文字への自動変換を制御すれば良い。
https://developer.apple.com/documentation/swiftui/textfield/autocapitalization(_:)
TextField("ユーザー名", text: $vm.userName) .autocapitalization(.none) TextField("メールアドレス", text: $vm.emailAddress) .autocapitalization(.none)