20210108のSwiftに関する記事は6件です。

iOSアプリで位置情報を取得するときに配慮する点をまとめてみた①

今回は、iOSアプリで位置情報を取得するときに配慮しなくてはいけない点を自分なりにまとめてみようと思います。

はじめに

私は今、現在地から飲食店を検索するアプリを開発しています。
※App Storeにリリースしているので良かったらダウンロードしてみて下さい!
ふっぴーくん

自分の現在地から飲食店を検索するアプリなので、もちろん、現在地を取得しなくてはいけません。だったら、現在地を取得して「はい、終わり!」というシンプルなものではありませんでした。

まずは、現在地を取得するために最低限必要な手順から書いていきたいと思います。

手順

iOSアプリで位置情報を取得するためには、

1.プライバシー情報取得の許可
2.CoreLocationフレームワークをインポート、CLLocationManagerクラスを初期化
3.位置情報サービスを使用するために、ユーザーに許可をリクエスト
4.CLLocationManagerDelegateプロトコルを採用し、デリゲートメソッドを実装

おおまかには、こんな感じの手順で位置情報が取得できます。
では、順を追って説明していきます。

1.プライバシー情報取得の許可

iOSアプリでは、プライバシーに関わる情報を取得する際に許可が必須です。
位置情報もプライバシーに関わる情報なので、位置情報サービスを使用する前に許可を貰わないと使うことができません。

許可を得るには、info.plistにて使いますよ!という意思表示を示しましょう。
Key:Location When In Use Usage Description
Value:任意の文字列(例:このアプリは位置情報サービスを使います。)

私の場合はこんな感じです↓
スクリーンショット 2021-01-08 11.43.11.png

2.フレームワークをインポート、クラスを初期化

まずは、位置情報関連のサービスがアプリ内で使えるようにCoreLocationフレームワークをインポートします。

ViewController
import CoreLocation

そして、CoreLocationフレームワークに含まれているCLLocationManagerクラスを初期化します。
このクラスは、位置情報の機能を管理してくれています。

このクラスを、メンバプロパティ(クラス内で定義されるプロパティ)として
ViewControllerで宣言しインスタンスを初期化していきます。

ViewController
import CoreLocation

class ViewController: UIViewController {
 //CLLocationManagerクラスのインスタンスlocationManagerをメンバプロパティとして宣言
 var locationManager: CLLocationManager = {
  // 取得精度の設定(1km以内の精度)
  locationManager.desiredAccuracy = kCLLocationAccuracyKilometer
  // 位置情報取得間隔(5m移動したら位置情報取得)
  locationManager.distanceFilter = 5
  //インスタンスを初期化
  var locationManager = CLLocationManager()
  return locationManager
 }()
}

CLLocationManagerクラスには、位置情報取得の精度位置情報取得の間隔など色々な設定を自分で調節できます。
宣言と同時に書いてあるdesiredAccuracydistanceFilterプロパティの事ですね。

自分のアプリに合った設定をすることで、バッテリーが長持ちするというメリットがあります。
位置情報サービスはかなりの電力を使いますので、こういう所も配慮しなくてはいけませんね。

3.ユーザーに許可をリクエスト

位置情報サービスを使用するときは、ユーザーに対して許可をリクエストしなくてはいけません。
今回は、アプリの使用中に許可をリクエストしたいのでCLLocationManagerクラスのメソッドrequestWhenInUseAuthorization()を使います。

ViewController
import CoreLocation

class ViewController: UIViewController {
 //CLLocationManagerクラスのインスタンスlocationManagerをメンバプロパティとして宣言
 var locationManager: CLLocationManager = {
  // 取得精度の設定(1km以内の精度)
  locationManager.desiredAccuracy = kCLLocationAccuracyKilometer
  // 位置情報取得間隔(5m移動したら位置情報取得)
  locationManager.distanceFilter = 5
  //インスタンスを初期化
  var locationManager = CLLocationManager()
  return locationManager
 }()

 override func viewDidLoad() {
    super.viewDidLoad()
    // ユーザーに許可をリクエスト
    locationManager.requestWhenInUseAuthorization()
  }
}

ここで一旦、ビルドしてみたいと思います。

そもそも端末の位置情報サービスがオフになっているとこんな感じのアラートが表示されます。
設定を選択することで設定アプリに画面遷移しますので、タップして位置情報サービスをオンにしてみましょう。

※ただし、アプリをインストール後の初回起動のみしか表示されません。

位置情報サービスをオンにしてから再度、アプリを起動すると、こんな感じのアラートが表示されます。
このアラートは、requestWhenInUseAuthorization()を呼ぶと表示されます。

1度だけ許可Appの使用中は許可などの選択肢がありますね。
これは位置情報サービスの認証ステータスでいくつかの種類があります。

認証ステータスは、CLAuthorizationStatusにまとめており、
アプリを初めて起動した時はnotDeterminedになっています。

なので現状、ビルドしたアプリはnotDetermined状態という事ですね。

分かりやすいように認証ステータスをまとめてみました↓

認証ステータス 意味
authorizedAlways 常に許可
authorizedWhenInUse Appの使用中は許可(1度だけ許可も含まれる)
denied 許可しない
restricted 端末の位置情報サービスが許可されていない
notDetermined 位置情報サービスを使用できるかどうかを選択していない

因みに、requestWhenInUseAuthorization( )はnotDetermined以外だと呼ばれないです。
これはリクエストを許可しているのにアラートが表示されてしまうのを防ぐためだと思われます。

ユーザーにリクエストしたのはいいですが、まだ現時点では位置情報を取得することは出来ません。

4.デリゲートメソッドの実装

CLLocationManagerDelegateプロトコルを準拠し、デリゲートメソッドを実装していきます。

ViewController
import CoreLocation

class ViewController: UIViewController {
 //CLLocationManagerクラスのインスタンスlocationManagerをメンバプロパティとして宣言
 var locationManager: CLLocationManager = {
  // 取得精度の設定(1km以内の精度)
  locationManager.desiredAccuracy = kCLLocationAccuracyKilometer
  // 位置情報取得間隔(5m移動したら位置情報取得)
  locationManager.distanceFilter = 5
  //インスタンスを初期化
  var locationManager = CLLocationManager()
  return locationManager
 }()

 override func viewDidLoad() {
    super.viewDidLoad()
    // ユーザーに許可をリクエスト
    locationManager.requestWhenInUseAuthorization()
    // デリゲート先を自分のViewControllerにする
    locationManager.delegate = self
  }
}

// ViewControllerにCLLocationManagerDelegateプロトコルを準拠
extention ViewController: CLLocationManagerDelegate {
    // CLLocationManagerクラスのインスタンス初期化および、認証ステータスが変更されたら呼ばれるメソッド
    func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
        // アプリの現在の認証ステータス
        let status = manager.authorizationStatus

        switch status {
        case .authorizedAlways, .authorizedWhenInUse:
            // 位置情報取得を開始
            manager.startUpdatingLocation()

        case .notDetermined:
            // ユーザーに許可をリクエスト
            manager.requestWhenInUseAuthorization()

        case .denied:
            break

        case .restricted:
            break

        default:
            break
        }
    }
  // 位置情報を取得・更新したら呼ばれるメソッド
  func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        // 位置情報を取得する
        guard let gps = manager.location?.coordinate else {
            // 取得出来なかったらアラートを表示したりなど
            return
        }
        // 位置情報取得を停止
        manager.stopUpdatingLocation()
        //経度と緯度を出力する
        let lat = gps.latitude
        let lng = gps.longitude
        print("経度:\(String(describing: lat)), 緯度:\(String(describing: lng))")
    }
}

requestWhenInUseAuthorization()によって、認証ステータスが決定したら
locationManagerDidChangeAuthorizationデリゲートメソッドが呼ばれ、そのメソッド内で処理していきます。

とりあえずは、ユーザーが.authorizedAlways, .authorizedWhenInUseを選択した場合の処理と
.notDeterminedを選択した場合の処理を記述しています。

位置情報を取得するためには、startUpdatingLocation()メソッドを呼ばなければいけません。
このメソッドにより、位置情報取得を開始してくれます。

そして、位置情報を取得したらlocationManager(_: didUpdateLocations:)デリゲートメソッドが呼ばれ、
ここでやっと位置情報を取得することが出来ます。

では、長くなりましたが、ここからが本題となります。

本題

題名にも書いてあるように、位置情報を取得する時には配慮しなくてはいけない点がいくつかあります。
まずは、端末の位置情報サービスの有無です。

端末の位置情報サービスがオフだった場合、「位置情報サービスをオンにして下さい」というアラートが
表示されると思いますが、アプリをインストール後の初回起動のみしか表示されません。

なので、2回目以降の起動時は端末の位置情報サービスがオフだった場合、何も起こりません。
もちろん、位置情報を取得することも出来ません。

では、どうすれば良いのでしょうか?

端末の位置情報サービスの有無をチェックする

自分で端末の位置情報サービスの有無をチェックするコードを記述しなくてはいけません。

しかし、そんなに難しい訳ではなく、CLLocationManagerクラスには便利なメソッドがあります。
それは、locationServicesEnabled()です。

このメソッドは、端末で位置情報サービスが有効になっているかどうかを示すBool値を返します。
trueの場合は位置情報サービスオン、falseの場合は位置情報サービスオフ

では、このメソッドを先ほどのソースコードに組み込んでいきましょう。
画面表示後に呼ばれるViewDidApperメソッドに記述します。

そして、実際にアラートを表示したいので
私が過去にQiitaに投稿した記事のやり方でアラートを表示したいと思います↓
UIAlertControllerをファイルを分けて実装してみる

ViewController
import CoreLocation

class ViewController: UIViewController {
 //CLLocationManagerクラスのインスタンスlocationManagerをメンバプロパティとして宣言
 var locationManager: CLLocationManager = {
  // 取得精度の設定(1km以内の精度)
  locationManager.desiredAccuracy = kCLLocationAccuracyKilometer
  // 位置情報取得間隔(5m移動したら位置情報取得)
  locationManager.distanceFilter = 5
  //インスタンスを初期化
  var locationManager = CLLocationManager()
  return locationManager
 }()

 override func viewDidLoad() {
    super.viewDidLoad()
    // ユーザーに許可をリクエスト
    locationManager.requestWhenInUseAuthorization()
    // デリゲート先を自分のViewControllerクラスにする
    locationManager.delegate = self
  }

 override func viewDidAppear(_ animated: Bool) {
     super.viewDidAppear(animated)
     // 位置情報サービスがオフの場合
     if !CLLocationManager.locationServicesEnabled() {
        // アラートを表示して位置情報サービスをオンにするようにユーザーに促したりする
        Alert.okAlert(title: "位置情報サービスを\nオンにして下さい", message: "「設定」アプリ ⇒「プライバシー」⇒「位置情報サービス」からオンにできます")
     }
  }
}

// ViewControllerにCLLocationManagerDelegateプロトコルを準拠
extention ViewController: CLLocationManagerDelegate {
    // CLLocationManagerクラスのインスタンス初期化および、認証ステータスが変更されたら呼ばれるメソッド
    func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
        // アプリの現在の認証ステータス
        let status = manager.authorizationStatus

        switch status {
        case .authorizedAlways, .authorizedWhenInUse:
            // 位置情報取得を開始
            manager.startUpdatingLocation()

        case .notDetermined:
            // ユーザーに許可をリクエスト
            manager.requestWhenInUseAuthorization()

        case .denied:
            break

        case .restricted:
            break

        default:
            break
        }
    }
  // 位置情報を取得・更新したら呼ばれるメソッド
  func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        // 位置情報を取得する
        guard let gps = manager.location?.coordinate else {
            // 取得出来なかったらアラートを表示したりなど
            return
        }
        // 位置情報取得を停止
        manager.stopUpdatingLocation()
        //経度と緯度を出力する
        let lat = gps.latitude
        let lng = gps.longitude
        print("経度:\(String(describing: lat)), 緯度:\(String(describing: lng))")
    }
}

位置情報サービスをオフの状態でビルドしてみると、ちゃんと表示されました。
とにかく、これでアプリは端末の位置情報サービスの有無をチェックするようになりましたね。

これで一件落着!と思いますが、ここから更に想定して実装していかなければいけません。

例えば、ユーザーがアラートでOKを選択して位置情報サービスをオフにしたままアプリに戻ったとしましょう。
どうなると思いますか?

流れとしてはこんな感じ↓
位置情報サービスオフ > アプリ起動 > アラート表示(OKを選択) > 設定アプリを開く > 何もしないまま戻る > ???

このままだと、アプリは何もチェックしません

この解決方法はアプリがバックグラウンド状態から戻った時に位置情報サービスの有無をチェックすれば解決します。
では、どのようにするのかをまとめようと思いましたが、記事が長くなってしまったので後日、書こうと思います。

ここまで読んで下さって、ありがとうございます!
もし、ここは違うよ!というのがありましたら気軽にコメントして下さい。

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

Cannot convert value of type 'DocumentSnapshot' to expected argument type 'QueryDocumentSnapshot'

QueryDocumentSnapshotからモデルをインスタンス化していたので、DocumentSnapshotから生成しようとするとエラーが出た。

DocumentSnapshotとQueryDocumentSnapshotの違い

こちらの記事が分かりやすい。
https://note.com/shion_consul/n/nd45e9f385696

DocumentSnapshotは.getDocument()で取得する単一のデータ。ただし存在の保証はない。
QuerySnapshotはdb.collection("posts").addEventLitener()などで取得してきたドキュメントの塊で、その一つ一つがQueryDocumentSnapshotである。QueryDocumentSnapshotはQueySnapshotの子なので存在の保証がされているっぽい。

本題

私のケースですとQueryDocumentSnapshotを使ってModelからインスタンスを生成していたのですが、QueryDocumentSnapshotはDocumentSnapshotを継承したクラスだったので以下のようにするとエラーは消えました。

struct Post {
   var name: String?

   init(document: DocumentSnapshot) {
        guard let data = document.data() else { return } // ここはあまり良くない書き方だと思う
        if let name = data["name"] as? String {
            self.name = name
        }
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Xcode12.3】Appleプラットフォームのアプリ開発入門【SwiftUI】<1/8作成中>

SwiftUI とは

SwiftUIとは、AppleプラットフォームのアプリケーションUIを、開発言語をSwiftとした宣言型シンタックス(Declarative Syntax)で構築するUIフレームワークです。

SwiftUI同様、Apple製品のアプリケーションUIを構築するフレームワークとしてはFlutterが有名ですが、その違いを以下の表にまとめました。

SwiftUI Flutter
対応プラットフォーム Appleのみ iOS, Android, Web
IDE Xcode Android Studio, VS code
記述形式 宣言的シンタックス 宣言的シンタックス
開発言語 Swift Dart, Widget
コード量 少ない 多い
拡張性 Flutterに劣る 高い

Appleプラットフォームのアプリ開発にあたって、Apple公式のフレームワークSwiftUIに注目していきます。

SwiftUI

オブジェクト

システムボタン(Button)

スクリーンショット 2021-01-08 19.17.40.png

ボタン
Button(action: <実行メソッド>) { <ラベル> }

カラーピッカー(Color Picker)

スクリーンショット 2021-01-08 19.32.30.png

カラーピッカー
ColorPicker("<ラベル>", selection: .constant(.<既定色>))

日付ピッカー(Date Picker)

スクリーンショット 2021-01-08 19.37.47.png

日付ピッカー
DatePicker(selection: .constant(Date()), label: { Text("<ラベル>") })

グループ(Disclosure Group)

スクリーンショット 2021-01-08 19.43.42.png
↑閉じた状態

スクリーンショット 2021-01-08 19.44.11.png
↑開いた状態

グループ
DisclosureGroup("<ラベル>") { <コンテンツ> }

エディタボタン

スクリーンショット 2021-01-08 19.51.43.png
↑エディタが非アクティブな状態

スクリーンショット 2021-01-08 19.53.10.png
↑エディタがアクティブな状態

エディタボタン
EditButton()
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Xcode12.3】Appleプラットフォームのアプリ開発入門【SwiftUI】<1/9作成中>

SwiftUI とは

SwiftUIとは、AppleプラットフォームのアプリケーションUIを、開発言語をSwiftとした宣言型シンタックス(Declarative Syntax)で構築するUIフレームワークです。

SwiftUI同様、Apple製品のアプリケーションUIを構築するフレームワークとしてはFlutterが有名ですが、その違いを以下の表にまとめました。

SwiftUI Flutter
対応プラットフォーム Appleのみ iOS, Android, Web
IDE Xcode Android Studio, VS code
記述形式 宣言的シンタックス 宣言的シンタックス
開発言語 Swift Dart, Widget
コード量 少ない 多い
拡張性 Flutterに劣る 高い

Appleプラットフォームのアプリ開発にあたって、Apple公式のフレームワークSwiftUIに注目していきます。

SwiftUI

オブジェクト

システムボタン(Button)

スクリーンショット 2021-01-08 19.17.40.png

ボタン
Button(action: <実行メソッド>) { <ラベル> }

カラーピッカー(Color Picker)

スクリーンショット 2021-01-08 19.32.30.png

カラーピッカー
ColorPicker("<ラベル>", selection: .constant(.<既定色>))

日付ピッカー(Date Picker)

スクリーンショット 2021-01-08 19.37.47.png

日付ピッカー
DatePicker(selection: .constant(Date()), label: { Text("<ラベル>") })

グループ(Disclosure Group)

スクリーンショット 2021-01-08 19.43.42.png
↑閉じた状態

スクリーンショット 2021-01-08 19.44.11.png
↑開いた状態

グループ
DisclosureGroup("<ラベル>") { <コンテンツ> }

エディタボタン

スクリーンショット 2021-01-08 19.51.43.png
↑エディタが非アクティブな状態

スクリーンショット 2021-01-08 19.53.10.png
↑エディタがアクティブな状態

エディタボタン
EditButton()

フォーム(Form)

スクリーンショット 2021-01-09 13.14.02.png

フォーム
Form { <コンテンツ> }

グループボックス(Group Box)

スクリーンショット 2021-01-09 13.19.20.png

グループボックス
GroupBox(label: <ラベル>) { <コンテンツ> }

ラベル(Label)

スクリーンショット 2021-01-09 13.22.14.png

ラベル
Label("<ラベル>", systemImage: "<記号>")

リンク(Link)

スクリーンショット 2021-01-09 13.25.55.png

リンク
Link(destination: <URL>) { Text("<ラベル>") }

リスト(List)

スクリーンショット 2021-01-09 13.34.39.png

リスト
List { <コンテンツ> }

ナビゲーションビュー(Navigation View), ナビゲーションリンク(Navigation Link)

Simulator Screen Shot - iPhone 12 - 2021-01-09 at 13.57.48.png
↑画面遷移前

Simulator Screen Shot - iPhone 12 - 2021-01-09 at 13.57.51.png
↑画面遷移後

ナビゲーションリンク
NavigationView {  //NavigationLinkはNavigationViewのスコープ内で記述
    NavigationLink(destination: <Viewプロトコルに準拠したインスタンス>) { <ラベル> }
}

アウトライン(Outline Group)

スクリーンショット 2021-01-09 17.01.28.png
↑ListなしVer.

スクリーンショット 2021-01-09 17.02.36.png
↑ListありVer.

アウトライン(実装が難解だったため、ソースコードを記述)
struct CityData: Identifiable {  // OutlineGroupで使用するデータはIdentifiableプロトコルに適合していなければならない
    //構造体"CityData"のプロパティはid, name, sitesの3つ
    let id = UUID()  //UUID(ユニークID)…128bitの16進数
    let name: String
    var sites: [CityData]?  // タイプメソッドresult()によって値が後から代入される

    static func result() -> [CityData] {  // タイプメソッド(静的メソッド)の定義はstaticキーワードを付加
        let city1 = [CityData(name: "Site A"), CityData(name: "Site B")]
        let city2 = [CityData(name: "Site C"), CityData(name: "Site D")]

        return [CityData(name: "city1", sites: city1),
                CityData(name: "city1", sites: city1)]
    }  // タイプメソッドによってsitesの値が以下のようになる
       // [name: city1, sites: Optional([name: site A, sites: nil], [name: Site B, sites: nil]) 
       //  name: city2, sites: Optional([name: site C, sites: nil], [name: Site D, sites: nil])]
}

struct ContentView: View {
    var body: some View {
        List {
            ForEach(CityData.result()) { city in  // CityData.sitesのname: city1, city2のそれぞれに対して以下の処理を実行
                OutlineGroup(city, children: \.sites) { site in  // CityData.sites.sitesのname: site A, site Bのそれぞれに対して以下の処理を実行
                    Text(site.name)
                }
            }
        }
    }
}
Identifiableプロトコル
public protocol Identifiable {
    associatedtype ID: Hashable  //型パラメータ(=プレースホルダ)"ID"は"Hashable"プロトコルに適合
    var id: Self.ID { get }  //プロパティ"id"は読み取り専用であり、インスタンス自身の型パラメータ"ID"に適合
}
Hashableプロトコル
public protocol Hashable{
    func hash(into hasher: inout Hasher)
    var hashValue: Int { get }  
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SwiftのTableViewの基本の基

SwiftのTableViewの基本の基

非エンジニアで最近Swiftを勉強している友人に「TableViewって何??」と質問された際を想定し、TableViewの基本についてまとめます。

※本記事では実装方法ではなく、各知識・文言などについてまとめています。
※主に自身の毎日の復習・学習の機会創出、アウトプットによる知識の定着を目的としております。暖かい目で読んで頂けますと幸いです。

TableViewとは

正式にはUITableViewクラス。
UITableViewはアイテム(Cell)を行ごとに表示する機能です。

よく、Todoアプリやニュースアプリなどで、同じようなUIでそれぞれの内容(写真・文言・メッセージ)が異なる情報が上から下まで並んでおりスクロールしてみるような機能があるかと思います。
その表示する機能として用いられているのがTableViewになります。

一番イメージしやすい例:ツイッターやインスタなどのSNS、〇〇ニュースなどのニュースアプリ

※ちなみにUITableViewは縦スクロールだけ。横スクロールは別のクラス

UITableViewクラスは縦スクロールだけです。
※私の認識が間違っていたらすみません。

ですので、よくライブ配信系のアプリなどで見る、1画面内に縦スクロールと別に横スクロールで配信者一覧を見れる状態の者はCollectionViewを用いていることが多くあります。

※CollectionViewのまとめについては後日

UITableViewのCellについて

UITableViewの中の一つ一つ(行)はUITableViewCellクラスで出来ています。
UITableView上にUITableViewCellをズラーと表示することとなります。

UITableViewCellの位置:sectionとrowについて

UITableViewの中でも、よく区切られて表示されている場面があると思います。
例えば、設定画面では「規約関連」「サービスについて」「その他」などのそのくくりのタイトルがあって、その中に選択肢となるCellがあるようなUIはよく見るかと思います。

その構成で指定するのがsectionとrowです。

section

一言で言うと「rowをまとめたグループ」です。

先ほどの例で言う 【「規約関連」「サービスについて」「その他」】の大きなくくりについてです。
1sectionの中に複数のrow(cell)が入ることになります。

row

一言で言うと「1行(=1cell)」です。
rowが最小単位で1rowにつき、1cell配置されます。

・SNSなどで区切りなくずらーと表示されるのはsection:1,cell:n個の構成です。
・section:3で各cell:5で作成すると、合計15cellできることになります。
※プログラムで各sectionごとのcellの個数は指定できます。

UITableViewのデリゲート・プロトコル

UIViewController内に以下のデリゲートをセットします。

delegate

・UITableViewDelegateプロトコル
UITableViewで発生するイベントを処理するためのデリゲートを設定するプロパティ

dataSource

・UITableViewDataSourceプロトコル
UITableViewで表示するデータを供給するためのプロパティ

※私はよくコードを入力して起動すると、「tableviewが動かない」「データが表示されない」場合があるのですが、大体デリゲートを設定し忘れています。これはそれぞれのデリゲートがこのような働きをになっているからですね。

※プロトコル:クラスや構造体が実装するプロパティとメソッドを定義する機能。UIViewControllerではプロトコルを実装して、プロトコル内に定義されているプロパティとメソッドを必ず実装する必要がある。

デリゲートメソッド

※基本的に利用するものだけ説明します。他にもありますので、気になる方はこちらの記事が非常に参考になると思います。
UITableViewのデリゲートメソッドまとめ

※tableviewは「numberOfRowsInSection」「..cellForRowAt..」の二つのメソッドがあれば最低限動作します!

numberOfRowsInSection

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 1 //Int
    }

numberOfRowsInSectionの言葉通り、セクション内のrow(=cell)の数を指定します。

numberOfSections

func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

numberOfSectionsの文字の通り、セクションの個数を設定します。

cellForRowAt

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) // カスタムセルの場合はwithIdentifierなど変更
return cell
}

cellForRowAtの文字通り、各rowごとにcellを指定します。
具体的にはcellのオブジェクトを作成し、戻り値に設定します。

rowごとに設定できるため、rowやsctionを条件にif・swichなどで戻り値となるcellのオブジェクトを動的に変えることができます。

didSelectRowAt

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {

}

文字通り、tableviewがタップされた際の処理を記載します。
IndexPathからタップされたsectionとrowを取得できますので、それを元に、処理内容を動的に決めることができます。

willDeselectRowAt

func tableView(_ tableView: UITableView, willDeselectRowAt indexPath: IndexPath) -> IndexPath? {

return indexPath
}

cellがアンタップにされた際にどのcellを非選択状態にするか指定します。

didDeselectRowAt

func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {

}

cellが非選択状態にされた際に呼び出されます

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

String.addingPercentEncoding(withAllowedCharacters:) の引数

String.addingPercentEncoding(withAllowedCharacters:) が何をエンコードするか気になったので調べてみました。
ついでに URLComponents の queryItems の value 部分と、JavaScriptCore の encodeURI および encodeURIComponent とも比較してみました。悩ましい。
ちなみに、記号は全部エンコードしちゃおうと思って .alphanumerics を使うと、アクセント符号付きのアルファベット (é など) やいわゆる全角英数字がエンコードされないという罠があります。

CharacterSet ! " # $ % & ' ( ) * + , - . / : ; < = > ? @ [ \ ] ^ _ ` { | } ~
urlFragmentAllowed
urlHostAllowed
urlPasswordAllowed
urlPathAllowed
urlQueryAllowed
urlUserAllowed
URLComponents
encodeURI
encodeURIComponent
import Foundation
import JavaScriptCore

let asciiSymbols = (0x20...0x7e)
    .map { Unicode.Scalar($0) }
    .filter { !CharacterSet.alphanumerics.contains($0) }

let allowdChars = { (str: String) -> String in
    let regexp = try! NSRegularExpression(pattern: "%[0-9A-F][0-9A-F]")
    let range = NSRange(str.startIndex..., in: str)
    return regexp.stringByReplacingMatches(in: str, range: range, withTemplate: "")
}

let urlComponents = { () -> String in
    var comp = URLComponents(string: "https://example.com/")!
    comp.queryItems = [URLQueryItem(name: "q", value: asciiSymbols.map { String($0) }.joined())]
    return allowdChars(comp.url!.absoluteString.components(separatedBy: "=")[1])
}()

let context = JSContext()!
context.evaluateScript("var ascii = ''; for (var i = 0x20; i < 0x7f; i++) ascii += String.fromCharCode(i)")
context.evaluateScript("var result = encodeURI(ascii)")
let encodeURI = allowdChars(context.objectForKeyedSubscript("result")!.toString()!)
context.evaluateScript("var result = encodeURIComponent(ascii)")
let encodeURIComponent = allowdChars(context.objectForKeyedSubscript("result")!.toString()!)

let charsets: [(String, CharacterSet)] = [
    ("urlFragmentAllowed", .urlFragmentAllowed),
    ("urlHostAllowed",     .urlHostAllowed),
    ("urlPasswordAllowed", .urlPasswordAllowed),
    ("urlPathAllowed",     .urlPathAllowed),
    ("urlQueryAllowed",    .urlQueryAllowed),
    ("urlUserAllowed",     .urlUserAllowed),
    ("URLComponents",      CharacterSet(charactersIn: urlComponents)),
    ("encodeURI",          CharacterSet(charactersIn: encodeURI)),
    ("encodeURIComponent", CharacterSet(charactersIn: encodeURIComponent)),
]
print("|CharacterSet|" + asciiSymbols.map { String($0) }.map { ($0 == "|" ? "&#124;" : $0) + "|" }.joined())
print("|:--|" + asciiSymbols.map { _ in ":-:|" }.joined())
for cs in charsets {
    print("|\(cs.0)|" + asciiSymbols.map { (cs.1.contains($0) ? "" : "✅") + "|" }.joined())
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む