20210105のSwiftに関する記事は13件です。

[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を使います。

FirstViewContoroller
import 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
    }

//ほかには、何かしらの方法で遷移するコード書く

}

NextViewController
import 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に表示される値を変更毎に更新することができます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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の感覚でいると見落としてしまうと思う。というか、これで一時間悩んだ。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Swift】SwityJSONを使用して占いAPIのJSONデータから簡単に情報を取得する

占いを無料で提供しているAPIがあったので、Swiftで呼び出そうと思ったらJSONの子要素の名前が日付(2021/01/01)のような形式で戸惑ってしまった。
SwiftyJSONを使用することで解決出来そうなので試してみた。

開発環境

Xcode 12.3
SwiftyJSON 5.0.0

SwiftyJSONを使用し、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星座分が続きます

        ]
    }
}

参考

SwiftyJSON

使用したAPI

Web ad Fortune 無料API
利用の条件があり、商用利用する場合は有料版を使うようにしてください。

まとめ

SwiftyJSONを使用したら本当に簡単にJSONデータから情報を取得できた。
とても便利なライブラリなので理解をもっと深めたい。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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")
}

こんな感じでした。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

TextField付きアラート

はじめに

ちょっと何かを入力させる時に、いちいちUI配置したりしなくて済むので便利で使える。

完成形
スクリーンショット 2021-01-05 15.23.20.png

環境

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を複数書く

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

文字列(リソース名)で画像を取得してImageViewに貼り付ける(kotiln)

以前書いた記事
画像名を文字列で指定してImageViewに貼り付けるコード(android)

これにLGTMしていただいていたので見てみたのですが、微妙だったので書き直します(ついでにkotlinで)
リソース名からImageViewに画像をセットするメソッドは、swiftにはあるんですがkotlinだとありそうでないです。

ちなみにswiftでは

imageView.image = UIImage(named:"hoge.png")

こんな感じでimageViewに画像をセットできます。

同じようなものをkotlinで再現してみます。

resourceIdを文字列から取得

StringExtensions.kt
fun String.getResourceId(context: Context): Int {
    return context.resources.getIdentifier(this, "drawable", context.packageName)
}

Stringの拡張クラスを定義しました

画像をImageViewにセット

ImageViewExtensions.kt
fun 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との比較

swift
imageView.image = UIImage(named:"some_image_name_string.png")
kotlin
imageView.setImageResourceByName("some_image_name_string")

大体似たような感じになったかと思います。
上記で定義したStringとImageViewの二つの拡張をコピペすればそのまま使えます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

UserDefaultsに保存されているデータを全て表示する

はじめに

UserDefaultsの中身を確認したい時に任意の場所に仕掛けて表示
忘備録です

コード

//UserDefaultsに保存されているデータを全て表示する
    for (key, value) in UserDefaults.standard.dictionaryRepresentation().sorted(by: { $0.0 < $1.0 }) {
      print("\(key) -> \(value)")
    }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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 が得られます。


:relaxed: Twitter @MaShunzhe

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Swift5]特定の時間を比較して前なのか後なのかを判定する方法

特定の時間の比較

例えばアプリを起動したときに、現在時刻と開発者側で予め設定している時刻を比較して、その状況にあった処理をおこないたい場合があったとします。(何時より前なら処理A, 後ならBなど)

そういった状況で使えるのが.compare( )メソッドです。
今回はこちらのメソッドの紹介をします!

記述方法

if '現在時刻'.compare('こちらで設定した時刻') == .orderedDescending {
  //ここで処理
  //.orderedDescendingは〜より後
}

if '現在時刻'.compare('こちらで設定した時刻') == .orderedAscending {
  //ここで処理
  //.orderedAscendingは〜より前
}

このように記述すればOK!
ふたつの時刻を.compare( )メソッドで比較して、以降なのか以前なのかを判定しております。
また、双方の時刻が同じかどうかを判定したい場合は.orderedSameを使えばいい。

参考にしてください!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

パスワードを入力する時に使用するTextFieldについて

問題

アカウント作成画面を作成していた時に、パスワード入力のViewにTextFieldを使用していたのだが、よく見るパスワード入力は入力文字が見えないようになっている。
SwiftUIを使用している場合、このようなTextFieldはどうやって実装するか分からなかったので調べた。

解決方法

TextFieldにオプションをつけるのかと思っていたが違うらしい。
SecureFieldというViewを使用すれば良いみたい。

https://developer.apple.com/documentation/swiftui/securefield

スクリーンショット 2021-01-05 0.35.11.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

非同期処理中にユーザー操作を無効にする方法

問題

アカウント作成という非同期処理があるのだが、作成中にはユーザーの操作を無効化したい。
ただ、調べてもユーザー操作を無効にする方法はないっぽい。
そこで、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)
            )
    }
}

ezgif.com-gif-maker (2).gif

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

TextFieldにてアルファベット入力時に先頭文字が大文字になってしまうことを防ぐ

問題

TextFieldを用いた画面を作成した際に入力時に先頭文字が自動的に大文字になってしまうことがある。
特に、Canvasを使用して画面の動作を確認する際に、キーボードからの入力だと、大文字を小文字に変換する術がなく、辛い。
ezgif.com-gif-maker.gif

解決方法

TextFieldのModifiersである、autocapitalization(_:)を使用して、大文字への自動変換を制御すれば良い。

https://developer.apple.com/documentation/swiftui/textfield/autocapitalization(_:)

TextField("ユーザー名", text: $vm.userName)
    .autocapitalization(.none)
TextField("メールアドレス", text: $vm.emailAddress)
    .autocapitalization(.none)

ezgif.com-gif-maker (1).gif

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む