20200528のiOSに関する記事は8件です。

【Flutter】Flutter製アプリをリリースしてみた、あとARとかも使ってみた話

アプリ概要

ゴルフのオリンピックゲームの計算やボールとピンまでの距離のAR測定,1−18までの乱数を生成することができる、ゴルフ幹事向けのアプリです。

App Store はこちら
Google Play Store はこちら

image.png

ファイル名ファイル名ファイル名

使用した技術

  • bloc
  • Provider
  • rxDart
  • Admob
  • Firebase Analytics
  • ARKit(iOSのみ)

ソースコードはこちら

https://github.com/Tetsukick/enGolf

AR技術のご紹介

iPhoneのみARKitを活用したプラグイン(arkit_flutter)が公開されていたので、そちらを活用して、タップした2点間の距離を測れるアプリを作成いたしました。

AR距離測定画面のソースコードを記載しておきます。
Sampleを80%ぐらい流用して簡単に開発できました。

import 'package:arkit_plugin/arkit_plugin.dart';
import 'package:flutter/material.dart';
import 'package:vector_math/vector_math_64.dart' as vector;

import 'dart:io';

class ARMeasureScreen extends StatefulWidget {
  @override
  _ARMeasureScreen createState() => _ARMeasureScreen();
}

class _ARMeasureScreen extends State<ARMeasureScreen> {
  ARKitController arkitController;
  vector.Vector3 lastPosition;

  @override
  void dispose() {
    arkitController?.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context){
    return Platform.isIOS ? Scaffold(
      body: Container(
        child: ARKitSceneView(
          enableTapRecognizer: true,
          onARKitViewCreated: onARKitViewCreated,
        ),
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.delete),
        backgroundColor: Colors.lightGreen,
        onPressed: () {
          arkitController.remove('point');
          arkitController.remove('text');
          arkitController.remove('line');
          lastPosition = null;
        },
      ),
    ) :
    Scaffold(
      body: Center(
        child: Text('Androidには対応しておりません\n現在開発中のためしばらくお待ち下さい。'),
      ),
    );
  }

  void onARKitViewCreated(ARKitController arkitController) {
    this.arkitController = arkitController;
    this.arkitController.onARTap = (ar) {
      final point = ar.firstWhere(
            (o) => o.type == ARKitHitTestResultType.featurePoint,
        orElse: () => null,
      );
      if (point != null) {
        _onARTapHandler(point);
      }
    };
  }

  void _onARTapHandler(ARKitTestResult point) {
    final position = vector.Vector3(
      point.worldTransform.getColumn(3).x,
      point.worldTransform.getColumn(3).y,
      point.worldTransform.getColumn(3).z,
    );
    final material = ARKitMaterial(
        lightingModelName: ARKitLightingModel.constant,
        diffuse: ARKitMaterialProperty(color: Colors.blue));
    final sphere = ARKitSphere(
      radius: 0.006,
      materials: [material],
    );
    final node = ARKitNode(
      name: 'point',
      geometry: sphere,
      position: position,
    );
    arkitController.add(node);

    if (lastPosition != null) {
      final line = ARKitLine(
        fromVector: lastPosition,
        toVector: position,
      );
      final lineNode = ARKitNode(
          name: 'line',
          geometry: line
      );
      arkitController.add(lineNode);

      final distance = _calculateDistanceBetweenPoints(position, lastPosition);
      final point = _getMiddleVector(position, lastPosition);
      _drawText(distance, point);
    }
    lastPosition = position;
  }

  String _calculateDistanceBetweenPoints(vector.Vector3 A, vector.Vector3 B) {
    final length = A.distanceTo(B);
    return '${(length * 100).toStringAsFixed(2)} cm';
  }

  vector.Vector3 _getMiddleVector(vector.Vector3 A, vector.Vector3 B) {
    return vector.Vector3((A.x + B.x) / 2, (A.y + B.y) / 2, (A.z + B.z) / 2);
  }

  void _drawText(String text, vector.Vector3 point) {
    final textGeometry = ARKitText(
      text: text,
      extrusionDepth: 1,
      materials: [
        ARKitMaterial(
          diffuse: ARKitMaterialProperty(color: Colors.red),
        )
      ],
    );
    const scale = 0.001;
    final vectorScale = vector.Vector3(scale, scale, scale);
    final node = ARKitNode(
      name: 'text',
      geometry: textGeometry,
      position: point,
      scale: vectorScale,
    );
    arkitController.add(node);
  }
}

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

iOS13でショートカットのバックアップを作成する

前置き

\デデーン!/
C8A144FA-0D15-4497-9D09-87E76FE70F28.jpeg
目当てのショートカットがなかなか見つからなかったり、割と不便なのでこの機会に整理します。

iOSのバージョンによってはバックアップの復元方法が違う

iOS12での方法は既に先駆者様達が個人ブログ等でまとめてくださっているので、ここではiOS13でのバックアップ・復元方法について取り上げます。

必要なもの

・iCloud Drive
・ファイルApp
・ショートカットApp

バックアップ

9D25654D-66EC-4584-8019-AD6B06B762C5.jpeg
極論この2アクションで十分です。日時を書いたフォルダを作っておくと尚GOOD。

復元

43B2025D-7D15-48ED-8A2D-84DB01314980.jpeg
iCloudからリンクを取得するというのがミソです。DropboxのリンクではショートカットAppが開かないため復元できません。

おまけ追記

バックアップ・復元ができる実用的なやつを作りました。リンク

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

CleanArchitecture ファイルの自動生成(iOS・Swift)

CleanArchitectureではファイルを大量に生成するので、できれば手動ではなく自動で行いたい。そのための手段をまとめた。

Xcodeのテンプレート機能

メリット

Xcodeの機能として、テンプレートファイルを用意しておいておくと、ファイル生成時に選ぶことができる。
Xcodeの機能にすぎず、特別な設定は不要でプロジェクトの中身に何ら影響がない。テンプレートファイルは~/Library/Developer/Xcode/Templatesに置く。

デメリット

テンプレートファイルは何処かから拾ってくるか自分で書く・編集する必要がある。自分は下記から拾った。

参考

https://medium.com/swift2go/installing-the-clean-swift-template-in-xcode-6b4367006827

Kuri

メリット

普通に使いやすい

デメリット

ymlファイル・テンプレートファイルをプロジェクトルートに置かないといけない(プロジェクトの構成に影響がある)

参考

https://github.com/bannzai/Kuri
https://dev.classmethod.jp/articles/making-progress-ios-dev-using-kuri/
https://dev.classmethod.jp/articles/introduction-of-kuri-generate-command-option/

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

RxSwiftで検索+ページネーションをいい感じにする

iOSアプリの以下のような条件の検索画面を RxSwift でいい感じに実装する方法をメモします。

  • キーワードを入力して検索ボタンをタップすると検索開始する
  • 検索APIは結果が多い場合を想定して、ページ番号指定によるページネーションができる
  • 検索結果一覧の最後のアイテムを表示するタイミングで自動で次のページを読み込む
  • 最後のページまで読み込んだら終了する

実装方法

以下、検索画面 SearchView を想定したコードです。細かい説明はコメントとして書いてあります。また、ところどころに動作確認用の print 文が入っています。

  • 検索ボタンを押した時は search(by keyword: String) を実行する
  • 結果一覧の最後のアイテムを表示するタイミングで requestNextPage() を実行する
SearchView.swift
/// 検索画面
class SearchView {
    /// キーワードを入力して検索ボタンを押した時に実行する処理
    func search(by keyword: String) {
        print("[A] Start search - ", keyword)

        // 次のページを要求するイベントを発生させるためだけの Subject を用意
        let requestSubject = PublishSubject<Void>()
        self.nextPageRequestSubject = requestSubject

        // 実行中の検索をキャンセルしてから開始する
        searchDisposable?.dispose()
        searchDisposable = Observable
            // 次のページの要求イベントに検索キーワードを組み合わせる
            .combineLatest(
                // イベント発生のたびにページ番号をインクリメントする
                requestSubject.scan(Int(0), accumulator: { (page, _) -> Int in return page + 1 }),
                Observable.just(keyword)
            )
            // 検索APIでキーワードとページ番号を指定して実行する
            .flatMap { (arg) -> Observable<[Int]> in
                let (page, keyword) = arg
                print("[B] Send API request - ", keyword, page)
                return API.search(by: keyword, page: page)
                    .catchErrorJustReturn([])
                    .asObservable()
            }
            // 検索結果が空になったら止める
            .takeWhile { (ids) in
                !ids.isEmpty
            }
            // 前回までの検索結果に、新しい結果を足したものを
            .scan([]) { (ids, newIds) -> [Int] in
                ids + newIds
            }
            .subscribe(onNext: { (ids) in
                print("[C] Results - ", ids)
            })

        // 最初のページを要求する
        requestSubject.onNext(())
    }

    /// 検索結果一覧の最後のアイテムを表示する時に実行する処理
    func requestNextPage() {
        print("[D] Request next page")
        // 次のページを要求するイベントを発生させるだけ
        nextPageRequestSubject?.onNext(())
    }

    private var searchDisposable: Disposable?
    private var nextPageRequestSubject: PublishSubject<Void>?

}

説明用に検索APIの動きを想定した処理を次のように用意しました。

  • 1ページあたり5件のIDを取得できる
  • 検索結果は3ページまで存在する
API.swift
import RxSwift

/// API
enum API {
    /// キーワードで検索する(検索結果は1ページ5件まで返す)
    static func search(by keyword: String, page: Int) -> Single<[Int]> {
        // 検索APIで3ページまで取得できるようなケース
        return Single.create { (observer) -> Disposable in
            switch page {
            case 1: observer(.success([1,2,3,4,5]))
            case 2: observer(.success([6,7,8,9,10]))
            case 3: observer(.success([11,12,13]))
            default: observer(.success([]))
            }
            return Disposables.create()
        }
    }
}

実行結果

実際の操作を想定して次のようなコードを実行してみます。

let searchView = SearchView()

// ページ1のリクエスト 
searchView.search(by: "hoge")

// ページ2のリクエスト 
searchView.requestNextPage()

// ページ3のリクエスト 
searchView.requestNextPage()

// ページ4のリクエスト(存在しない)
searchView.requestNextPage()

// ページ5のリクエスト(存在しない)
searchView.requestNextPage()

結果はこちらです。

[A] Start search -  hoge
[B] Send API request -  hoge 1
[C] Results -  [1, 2, 3, 4, 5]
[D] Request next page
[B] Send API request -  hoge 2
[C] Results -  [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[D] Request next page
[B] Send API request -  hoge 3
[C] Results -  [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
[D] Request next page
[B] Send API request -  hoge 4
[D] Request next page
[D] Request next page

うまく動いてますね。

  • 検索中のキーワードや現在のページ番号といった状態の管理をしなくてよくなっている
  • 取得済みの全ページの結果が出力されるので、それでテーブルビュー等を更新するだけでよい
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

iOSの各種シミュレーターをfzfで選んで起動する

プリミティブな方法だと

$ xcrun simctl list

で、起動したいシミュレーターのUUIDを確認して

$ xcrun instruments -w 208E5166-9C5D-4CA5-A9DE-529AD562D311 # iPhone 11 Pro Max

とすると指定したシミュレーターが起動するのだが、Xcodeをアプデするとシミュレーターも更新されてしまうので、以前書き留めておいたUUIDが使えなくなる。また調べないといけなくてめんどくさい。

ということで、インクリメンタルに端末名で検索してEnterで起動するようにした。

.zshrc に以下を追記

.zshrc
function simc() {
  xcrun instruments -w $(xcrun simctl list | grep -v unavailable | grep -E "^\s" | grep -v ":" | fzf  | grep -oE "\((.+?)\)" | grep -oE ".{20,}" | head -n1 | perl -pe "s/(\(|\))//g" )
}

fzfが入ってないなら以下を実行。

$ brew install fzf

設定を読み込み直して実行。

source ~/.zshrc

simc.gif

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

ローカライズファイルをAndroidとiOSで共通管理する

はじめに

AndroidとiOSでサービスを展開している場合、同じ文言を使っている。
プラットフォームごとに管理することで文言の誤字が発生しやすく、コストも上がる。
ローカライズファイルを効率よく管理する方法を考えました

前提条件

  • AndroidとiOSで共通のリソースを扱う
  • 差分を追いやすいこと

githubを使って管理

  • オリジナルデータとなる文言とkeyはjsonにして管理
  • githubを使うことで差分を追いやすくなる
  • CIを使うことで自動化できる

ローカライズファイルを更新するツール

  • JSONからローカライズファイルを生成するのを簡単にするためにツールを作成した
  • t-osawa-009/JSONToString

使い方

1.オリジナルデータとなるJSONファイルを追加

[
    {
      "key": "hoge",
      "key_android": "hoge",
      "key_ios": "hoge",
      "value_android": "hoge",
      "value_ios": "hoge"
    },
]

2.ローカライズファイルの生成を設定する.JSONToString.ymlファイルを追加

outputs:
  - key: key
    value_key: value_ios
    output: Strings/Localizable.strings
    format: strings
    sort: asc
  - key: key
    value_key: value_android
    output: Strings/strings.xml
    format: xml

3.コマンドを実行

$ JSONToString --json_path Strings.json

4.生成したファイルを各プロジェクトに追加する

"hoge" = "hoge";
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="hoge">hoge</string>
</resources>

まとめ

  • githubで管理することで1元管理が可能になった
  • 運用を考えて、ツールを自作するのは有効である

参考リンク

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

【Swift】選択上限のある複数選択可能なTableViewの実装+スクロールしてもチェックマークがズレないようにする

選択上限をつけた複数選択可能なTableViewの実装が調べても出てこなかったので、簡単に作ってみました。

今回実現したいTableView
  1. 複数選択可能
  2. 選択状態がスクロールによって他のCellにズレない
  3. 選択できる数に上限をつける

サンプルプログラム

1. 複数選択を可能にする

MultipleTableView.swift
// tableViewのisMultipleTouchEnabledをtrueにする
isMultipleTouchEnabled = true

これだけで複数選択が可能になる。

2. 選択状態がスクロールによって他のCellにズレない

Modelに選択状態を持って、その値を元にCellinitする。

MultipleTableView.swift
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(
                         withIdentifier: R.reuseIdentifier.multipleTableViewCell.identifier, 
                         for: indexPath) as! MultipleTableViewCell
    cell.selectionStyle = .none
    cell.kind = kinds[indexPath.row]
    return cell
}
MultipleTableViewCell.swift
var kind: KindModel? {
    didSet {
       accessoryType = kind?.isSelected ?? false ? .checkmark : .none
       initLabel()
    }
}

選択状態のsetは次の 選択できる数に上限をつける と合わせて紹介します。

3. 選択できる数に上限をつける

選択状態に上限をつけるのは、TableViewに選択数のカウンタをつけるのが1番楽かなと思います。
もっとかっこいい方法はないかと考えたのですが、特に思いつきませんでした。。

MultipleTableView.swift
var selectedIndex: [Int] = []

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    if let row = selectedIndex.filter({ $0 == indexPath.row }).first {
        guard let removeIndex = selectedIndex.firstIndex(of: row) else { return }
        selectedIndex.remove(at: removeIndex)
        kinds[indexPath.row].isSelected.toggle()
        return
    }
    // 今回は上限を3にしました
    if selectedIndex.count > 2 { return }
    kinds[indexPath.row].isSelected.toggle()
    selectedIndex.append(indexPath.row)
}

これで上限の管理は完了です!

didDeselectRowAt 使いたかったけど、なぜか呼ばれなかった。
SOFにも同じような投稿がありましたが、解決ならずだったので諦めました。。

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

iOSにユーザーデータを保存する方法と、そのためのコードの書き方: UserDefaults、Core Data、Key Chain、CloudKit

データの保存はiOSアプリの持つ主要な機能です。たとえば、ユーザーが指定した色などの環境設定を保存したり、ウェブサイトのトークンをアプリに保存したり、ToDoリストのアプリを作ってタスクを保存したりすることができます。データをシステムに保存する方法はいくつもあります。

目次

User Defaults
Core Data
Keychain
iCloud CloudKit

この記事では、上記の方法それぞれについて、サンプルコードを使って解説します。

User Defaults

主な用途

わずか2〜3行のコードで、String, Int, Bool, やどんな Data オブジェクトも保存・取り出しができます。

通常は、ユーザーがオンボーディングを終えたかどうかや、天気アプリでのユーザーのデフォルト都市等、ユーザーのプリファレンスを保存するのに使用されます。

長所

  • わずか数行のコードで簡単に実装
  • App Extensions 間でも簡単に共有
  • クラウドにデータを保存でき、ユーザーの所有デバイス間で同期可能( 同じApple ID でサインイン)

短所

シンプルなデータの保存のみに使います。データベーススキームはありません。また Apple によると、UserDefaults ストレージが512 KBに達すると警告が発せられます。そして Apple のドキュメンテーションには、ストレージが1MBに達するとアプリが終了するとあります。

Currently, there is only a size limit for data stored to local user defaults on tvOS, which posts a warning notification when user defaults storage reaches 512kB in size, and terminates apps when user defaults storage reaches 1MB in size.
https://developer.apple.com/documentation/foundation/userdefaults/1617187-sizelimitexceedednotification

しかし、Stack Overflow のユーザーの一人は、1MB以上のデータを保存してもクラッシュを起こしていないと報告しています。

https://stackoverflow.com/a/41615483

ですから、ユーザーのプリファレンスや文字列・Int値をいくらか保存したい程度であれば心配することはありません。

使い方

//To store a value
UserDefaults.standard.set("Tokyo", forKey: "currentCity")
//To read a value
let currentCity = UserDefaults.standard.string(forKey: "currentCity")

(クラウド同期バージョン)の使い方

iCloudの機能を有効にする

プロジェクト設定の Signing & Capabilities タブで、プラスボタンをクリックします。

Screen Shot 2020-05-27 at 2.23.12 PM.png

そして、iCloud 機能を追加します

Screen Shot 2020-05-27 at 2.26.28 PM.png

Key-value storage をオンにします

Screen Shot 2020-05-27 at 2.26.54 PM.png

そして、すべてのUserDefaults.standardNSUbiquitousKeyValueStore()に置き換えるだけです:

let store = NSUbiquitousKeyValueStore()
store.set("Tokyo", forKey: "currentCity")
let currentCity = store.string(forKey: "currentCity")

資源

Appleの公式文書

Core Data

主な用途

Core Data には幅広い用途があります。私の個人的な開発では、Core Data を使ってユーザーの日記、ToDoアイテム、ブックマークを保存しています。

Core Data はユーザーの情報を既存の多くのアプリに保存するために広く使われています。

長所

  • 構造化データの保存(データベーススキームを設計する必要があります)
  • 数分以内にクラウド同期サポートを有効にできる

短所

  • データベーススキームを変更するたびに、新しいデータベースバージョンを作成する必要があります。そうしないとアプリがクラッシュしてしまいます。

使い方

新しいアプリ

新しいアプリを作成する場合は、プロジェクトの作成時に Use Core Data を選択できます。

Screen Shot 2020-05-27 at 2.35.06 PM.png

既存のアプリ

また、既存のアプリに Core Data を簡単に追加できます。

最初に、データモデルファイルを作成します。キーボードのcommand-Nキーを押して、Data Model を検索します。

Screen Shot 2020-05-27 at 2.37.09 PM.png

新しい Data Model にあなたのアプリの名前をつけてください。例えば、ここでは私のアプリの名前は CoreDataDemo なので、私は CoreDataDemo.xcdatamodeld を作りました。

そして AppDelegate.swift ファイルに次の行を追加します。

// MARK: - Core Data stack

lazy var persistentContainer: NSPersistentContainer = {
    let container = NSPersistentContainer(name: "CoreDataDemo")
    container.loadPersistentStores(completionHandler: { (storeDescription, error) in
        if let error = error as NSError? {
            print(error.userInfo)
        }
    })
    return container
}()

この行で、 CoreDataDemo をあなたのアプリの名前に置き換えてください。

let container = NSPersistentContainer(name: "CoreDataDemo")

データベース構造の作成

To-Doアイテムごとに次のプロパティを格納するTo-Doアプリを作成するとします:

  • To-Doタスク名
  • タスクの担当者
  • タスクの期限
1. .xcdatamodeld ファイルを開き、Add Entity をクリックします。

Screen Shot 2020-05-27 at 2.42.46 PM.png

作成した新規エンティティ・アイテムに TodoItem という名前を付けます。

作成した新しいアイテムをダブルクリックし、名前を付けるだけです。この例では、 TodoItem という名前を付けます。

新しいプロパティを追加する

Properties セクションのプラスアイコンをクリックして、新しいプロパティを追加します。

Screen Shot 2020-05-27 at 2.44.30 PM.png

ここでは、To-Doアプリに次のプロパティが必要です。

プロパティ名 タイプ
todoTaskName String
personName String
taskDeadline Date

タイプを設定するには、プロパティ名の横にあるドロップダウンメニューをクリックします:

Screen Shot 2020-05-27 at 2.47.09 PM.png

このような感じになります。

Screen Shot 2020-05-27 at 2.48.28 PM.png

Entity クラスのコードファイルを生成
  1. 作成した新しいエンティティ項目を選択します。右側のパネルで最後のセクションのアイコンを選択します :

Screen Shot 2020-05-27 at 3.05.23 PM.png

  1. Codegen 設定を Manual/ None に変更

以前の設定:

Screen Shot 2020-05-27 at 3.08.57 PM.png

新しい設定:

Screen Shot 2020-05-27 at 3.10.38 PM.png

  1. クラス定義ファイルを生成

最上部のメニューで Editor をクリックし、Create NSManagedObject subclass... をクリックします

こうしたサブクラスを生成することで、TodoItem を新規オブジェクトの作成に直接使用できるようになります。

Screen Shot 2020-05-27 at 3.11.37 PM.png

ファイルナビゲーターに以下のファイルが表示されます :

Screen Shot 2020-05-27 at 3.13.31 PM.png

新規レコードを作成
func addNewToDoItem(todoTaskName: String, personName: String, taskDeadline: Date) {
    let appDelegate = UIApplication.shared.delegate as! AppDelegate
    let context = appDelegate.persistentContainer.viewContext
    let newItem = TodoItem(context: context)
    newItem.todoTaskName = todoTaskName
    newItem.personName = personName
    newItem.taskDeadline = taskDeadline
    do {
        try context.save()
    } catch {
        print(error.localizedDescription)
    }
}
既存のレコードを取得
func getAllPlannerDays() -> [TodoItem] {
    let appDelegate = UIApplication.shared.delegate as! AppDelegate
    let context = appDelegate.persistentContainer.viewContext
    let savedPlaceFetch = NSFetchRequest<NSFetchRequestResult>(entityName: "TodoItem")
    do {
        let result = try context.fetch(savedPlaceFetch)
        if let convertedResult = result as? [TodoItem] {
            return convertedResult
        }
    } catch {
        return []
    }
    return []
}
変更
func modifyObject(_ object: TodoItem, newDeadline: Date) {
    object.taskDeadline = newDeadline
    //保存する
    let appDelegate = UIApplication.shared.delegate as! AppDelegate
    let context = appDelegate.persistentContainer.viewContext
    do {
        try context.save()
    } catch {
        print(error.localizedDescription)
    }
}
削除
func deleteObject(_ object: NSManagedObject) {
    let appDelegate = UIApplication.shared.delegate as! AppDelegate
    let context = appDelegate.persistentContainer.viewContext
    context.delete(object)
    //保存する
    do {
        try context.save()
    } catch {
        print(error.localizedDescription)
    }
}

プログラムから TodoItem をご自身のエンティティ名に置き換えてください。

特定のレコードをクエリ中

すべての TodoItem に対し personNameネコノヒー としてクエリしましょう

func getToDoItem(withPerson: String) -> [TodoItem] {
    let appDelegate = UIApplication.shared.delegate as! AppDelegate
    let context = appDelegate.persistentContainer.viewContext
    let savedPlaceFetch = NSFetchRequest<NSFetchRequestResult>(entityName: "TodoItem")
    savedPlaceFetch.predicate = NSPredicate(format: "personName = %@", withPerson)
    do {
        let result = try context.fetch(savedPlaceFetch)
        return result as? [TodoItem]
    } catch {
        return []
    }
    return []
}

let allTodo = getToDoItem(withPerson: "ネコノヒー")

ユーザーのコアデータをクラウドに同期する

すでにここにガイドを書いています:

https://qiita.com/MaShunzhe/items/2018455cab0803d78442

新しいデータベースバージョンの作成

データベースの新しいバージョン(プロパティの追加、変更、削除、または新しいエンティティの作成)を作成するたびに、データベースの新しいバージョンを作成する必要があります。

データベースモデルファイルを選択した状態で、「Editor エディター」をクリックし、「Add Model Version モデルバージョンの追加」をクリックします。

次に、ファイルナビゲーターで、データベースモデルファイルの左側にある三角形のアイコンをクリックします。

作成した新しいデータモデルバージョンをクリックします。

右側のパネルでファイルアイコンをクリックし、「Model Version モデルバージョン」を見つけて、作成した新しいモデルバージョンに切り替えます。

左側のパネルで、作成したバージョンに緑のチェックマークが付いていることを確認してください。

資源

Appleの公式文書

クラウドサポート付きCore Data

Core Dataでアプリ制作

KeyChain (キーホルダー)

主な用途

ユーザーの機密情報を保存する場合は、Key Chainに保存することをお勧めします。Keychainは、iOSがユーザーのウェブサイトのユーザー名とパスワードを保存する場所です。暗号化されており、ユーザーのパスコードまたはFace ID (Touch ID)でのみアクセスできます。

長所

  • セキュリティー
  • 保存された認証情報は、ユーザーのデバイス間で同期できます。

短所:

  • ユーザーはMac OS KeychainアプリでKeychainに保存したデータを見ることができます
  • 通常、開発者はkeychainに文字列を保存します。データを保存することはお勧めしません。

使い方

keychainにアクセスするのに必要なコードはいくつかありますが、私はkeychainに簡単にアクセスできるオープンソースのGithubリポジトリを見つけました。

https://github.com/evgenyneu/keychain-swift

資源

Appleの公式文書

CloudKit

長所

  • - データをプライベート・データベース、共有データベース、またはパブリック・データベース(すべてのユーザーが他のユーザーのレコードをフェッチできる)に保存できます。
  • CloudKitはAppleの無料クラウドサービスです。
  • CloudKitプライベート・データベースへの保存は、ユーザー自身のiCloudストレージスペースに対してカウントされます。
  • CloudKitパブリック・データベースへの保存は、割り当て制限に対してカウントされます。
  • ユーザーがアプリを削除してから再度インストールすると、CloudKitに保存されているデータをフェッチできます。

短所

  • ユーザーはオンラインである必要があります。また、CloudKitデータベースの使用にはサイズ制限があります。

https://developer.apple.com/icloud/cloudkit/

実装

私は以前、「CloudKit」を使用してユーザーの情報を保存および取得する方法についてのガイドを紹介しました:

https://qiita.com/MaShunzhe/items/bc21527259c5da7f66ed

https://qiita.com/MaShunzhe/items/9291c86a3d0442ddf759

資源

Appleの公式文書

CloudKitダッシュボードの使用

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