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

Xcode 製品版でprintを出力しない例

func print(_ object: Any) {
    #if DEBUG
      Swift.print(object)
    #endif
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

UIView, UIScrollView, UITableView の画像キャプチャのシンプルな実装

本記事の趣旨

下記の目的に対して、巧妙な実装をしている解説記事が多かったので、よりシンプルな実装を提案するものです。

  • 任意の View のキャプチャ画像を UIImage として取得する
  • 任意の UIScrollView / UITableView のコンテンツ全体の画像を UIImage として取得する

万が一うまくいかなかったらコメントで教えていただけると幸いです。

方法

1. 任意の View のキャプチャ画像を UIImage として取得する

ここでは、UIView の extension として記述します。

UIView+getImage.swift
import UIKit

extension UIView {

    func getImage() -> UIImage? {
        // 確実に不透明なら、第2引数は true がよい
        UIGraphicsBeginImageContextWithOptions(self.bounds.size, false, 0.0)
        guard let context = UIGraphicsGetCurrentContext() else { return nil }
        self.layer.render(in: context)

        let capturedImage: UIImage? = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()

        return capturedImage
    }
}

2. 任意の UIScrollView / UITableView のコンテンツ全体の画像を UIImage として取得する

キャプチャの瞬間だけ、UIScrollView / UITableViewframe.sizecontentSize に変更してしまう方針です。
UItableViewUIScrollView を継承しているので、UIScrollView の extension として記述します。
(先ほどの getImage() とconflict しないように別名のメソッド (getContentImage()) とします。)

UIScrollView+getContentImage.swift
import UIKit

extension UIScrollView {

    func getContentImage() -> UIImage? {
        UIGraphicsBeginImageContextWithOptions(captureSize, false, 0.0)
        guard let context = UIGraphicsGetCurrentContext() else { return nil }

        // 元の frame.size を記憶
        let originalSize = self.frame.size
        // frame.size を一時的に変更
        self.frame.size = self.contentSize
        self.layer.render(in: context)
        // 元に戻す
        self.frame.size = originalSize

        let capturedImage: UIImage? = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext();

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

Swiftの言語について

初めまして!きゅうすけと申します!
iOSエンジニアの私が、自社の定例イベント 「TokyoUppersBoost」 で学んだ基礎の部分をまとめたものです!
メモ感覚ではありますが、これ違うぞってことがあったらぜひ教えてくださいー!(● ˃̶͈̀ロ˂̶͈́)੭ꠥ⁾⁾

変数と定数

変数とは

再代入が必要な場合に使う
var

var name = "田中"

定数とは

再代入が不要な場合に使う
let

let name = "田中"

値の代入方法

定数は後から値の変更ができないから、代入しようとするとエラーになる

var name = "田中"
name = "佐藤"

let name = "田中"
name = "佐藤"//エラー

値の種類のこと。
Swiftにおいては変数や定数は、 必ずどれかの型に属していなければならないルールがある。
そのため、変数や定数を宣言するときは型の種類を指定する必要がある。

var drink: String = "コーヒー"

型推論

swiftの場合は文脈から読み取ってくれる → わざわざ明示的に型を書かなくてもいい!

// 型推論
let name = "田中"   //String型
let number = 10   //Int型
let Name = 15.5   //Double型

基本的な型

String:文字列
Int:整数
Float:浮動小数点(32bit長)
Double:動小数点(64bit長)
Bool:真偽値
Array:配列
Dictionary:辞書型

String型

文字列を表す型 ""で囲う
数値を入れようとするとエラーが出る

let name: String = "佐藤"

Int型

整数を表す型

let age: Int = 22

Double型 / Float型

小数点がある値を宣言する型。不動小数点型とも言われる。
Double型:大きい値(64bit)
Float型:小さい値(32bit)

Float型にしたい時はFloatと書く必要がある

let length = 125.55  //Double型
let width: Float = 100.55  // Float型

Bool型

true、falseを表す型。
Bool型で宣言している場合は、trueとfalseしか代入できない

let flag = true //Bool型

Array型

配列。複数の値が格納された型。
値を,で区切り、[]で囲む。
文字列の場合はString型で定義する。
0からカウントする。

let color: String = ["赤", "青", "黄色", "緑"]
let number: Int =  [1, 2, 3, 4]

 Dictionary型

name:キーと値を1つのまとまりとして複数のまとまりを1つの値にまとめる型。

let person = ["name": "田中", "age": 22]

Optional型と非Optional型

Swiftの非Optional型はnilを許容しない。
nil(いれられるものが何もない状態)を許さないからエラーが出る

hello = "こんにちは"
hello = nil        //error

Optional型
nilを代入することができる
型の最後に? または!をつける。

var name = String? 
var age = Int!

? と ! の入れ方の違い
? → 値が入るかもしれないし入らないかもしれない
! → nilを入れることができない 暗黙的アンラップ

アンラップについてはまだ理解できていない…(´・ω・`)

非Optional型
型の後に何も付けない。

var name = String
var age = Int

終わりに

ラップ、アンラップについても説明があったのですが、自分がまだちゃんと理解できていないため
もう少し教えてもらったり調べてから別で記事書こうと思ってます!!

私が参加している勉強会 「TokyoUppersBoost」への参加者さん大募集中です!!
一緒にiOSについて学びましょう・:*+.(( °ω° ))/.:+

↓私が作ったLPなのでよかったら見てください!٩( 'ω' )و
TokyoUppersBoostとは?

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

WKWebViewでローカルのindex.htmlを読み込んでscriptを実行してみた

ネットを介してのWebView表示ではなく、手元にあるHTMLをWebViewで表示、scriptタグ内のJSメソッドの呼び出し方法をまとめます。

SwiftでWebViewを表示させる場合、WKWebViewを使うことがほとんどだと思います。現在ではUIWebViewは非推奨です。そちらについては以下の記事がとても参考になります。
[参考元]:UIWebViewを使わない理由とWKWebViewを使う理由

環境

OS: Mojave 10.14.5
Xcode: 10.1
Swift 4.2

HTML

今回はindex.htmlをWKWebViewでロードし、<script>タグ内にあるJSメソッドを呼び出すことをします。
HTMLファイルはプロジェクト内に置きます。置き場は自由で構わないのですが、例として以下のように配置しておきます。

project
├─Resource
│ ├index.html
│ └sample.json
│
└─View
  └SomeViewController

HTMLのロード

ローカルのHTMLファイルはパスを取得する必要があり、取得にはBundle.main.pathを使用します。
仕様ではBundle.main.pathはプロジェクトのBuild Setting > Copy Bundle Resourcesにあるファイルを取得するようです。
まずこの項目を確認し、取得したいHTMLファイルがなければ追加しておきましょう。

[参考元]:Swiftでプロジェクト内のファイルを取得する際の注意点2つ

HTMLファイルのパスをURL型にキャストしてWebViewのloadFileURLの引数に与えます。これでWebViewにロードできました。

func loadLocalHTML() {
    guard let path: String = Bundle.main.path(forResource: "index", ofType: "html") else { return }
    let localHTMLUrl = URL(fileURLWithPath: path, isDirectory: false)
    webView.loadFileURL(localHTMLUrl, allowingReadAccessTo: localHTMLUrl)
}

Swift側からscript内のjsを実行

実行タイミング

<script>タグにあるメソッドは、HTML全てがロードし終わってから呼び出さないとメソッド呼び出しに失敗します。
ロードが終わったことを通知するdelegateがWKNavigationDelegateにあります。

delegateの通知先は自分であることの宣言を忘れずに。

SomeViewContoller
webView.navigationDelegate = self
SomeViewController
extension SomeViewController: WKNavigationDelegate {

    func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
        // ここでJSの実行をする
    }
}

JSの実行

evaluateJavaScriptを使います。引数のjavaScriptStringに、実行するメソッドをStringで渡します。
また、ここのStringではjsの文法で書く必要があります。jsの知見が乏しい筆者はここで躓きました。

[公式リファレンス]:evaluateJavaScript(_:completionHandler:)

<script>の中身は以下のようなものと仮定して、initHTML関数に引数を渡し実行する方法を解説します。

<script>
    function initHTML(id, jsonData, onload) {
        console.log('initHTMLだお');
        console.log(id, jsonData);
        onload();
    }
</script>

引数

Swiftは静的型付け、JSは動的型付けなので、ゆるい方向への値渡しです。
initHTMLの引数(id, jsonData, onload)は、Swift側ではそれぞれInt,Json,関数として渡します。

基本は文字列リテラル内での値の展開\()を使います。
Jsonに関しては、Jsonファイルを読み取ってData->Stringに変換して、値展開で渡しています。Stringで渡してもWebView側でJsonとして扱ってくれます。
関数はjsの書き方であるアロー関数を用いて書きましょう。

extension SomeViewController: WKNavigationDelegate {

    func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
        let intValue = 10
        guard let json = getLocalJson() else { return }
        guard let strJson = String(data: json, encoding: .utf8) else { return }
        let script = "initHTML(\(intValue), \(strJson), () => { console.log('Swiftから渡された値だお') });"
        webView.evaluateJavaScript(script) { object, error in
            print(error ?? "成功")
        }
    }

    func getLocalJson() -> Data? {
        guard let path = Bundle.main.path(forResource: "sample", ofType: "json") else { return nil }
        let url = URL(fileURLWithPath: path)
        let json: Data?

        do { json = try Data(contentsOf: url) }
        catch { json = nil }

        return json
    }
}

コンソールの確認

JSの実行ができたらコンソールを確認しましょう。
しかしWebViewで実行したJSコンソールログはXcodeでは見れません。
ブラウザであるSafariで見ることができます。開発 -> 端末名 -> index.jsと選択するとWebインスペクターが開きます。

webinspector.png

呼び出しがうまくいってログが出力されています!
表示がない場合は左上らへんにある更新マークを押しましょう。

次回

WebViewのJS内でprojectのソース(画像とか)にアクセスしたときに発生するエラー、Orijin null is not byAccess-Control-Allow-Origin編に続く...

WWDC19あとがき

SwiftUIめっちゃすごい。iOSのUI回りに激震が走りましたね。
ここまでUIの作成ハードルが下がると我々iOSエンジニアの価値が相対的に下がっていくのでは…
Combine frameworkの登場により、Rxライブラリは今後必要なくなるのかな。ただObserverパターンの考え方は必要なのでここはそれなりにハードルは依然高そう。

参考

iOS WKWebView ネイティブとローカルJavascript連携

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

Swift UI を使って簡単なSNS風(見た目だけ)を作りました。

SwiftUI を用いSNS風(見た目)を作ってみました

  • 現状のswiftUIの導入方法についてはこちらをご覧ください。
  • できたもの
  • サンプルコード
    • 縦スクロール
    • 画像のハメかた
    • 横スクロール


感想

今までより簡単にコードが書けるので学習コストもそんなにかからないと思いました!
flutter を勉強しようと思っているのですがどちらから勉強すればいいか迷っています。。。

 できたもの

 
sns風

サンプルコード

縦スクロール

まずはTableViewのような縦にスクロールできるものを作っていきます。

ContentView.swift
import SwiftUI

struct ContentView: View {


    var body: some View {
        NavigationView{
            List{
               Text("Row_1")
               Text("Row_2")
            }
        }.navigationBarTitle(Text("Groups").color(.green))
    }
}

こんな感じになるはずです(なぜかNavigationBarが表示されません。)
スクリーンショット 2019-06-07 18.52.17.png

また以下のように書くと
文字がいじれます。

ContentView.swift
struct ContentView: View {


    var body: some View {
        NavigationView{
            List{
                Text("Row_1")
                    .font(.headline)
                    .color(.red)
                Text("Row_2")
                    .font(.footnote)
            }
        }.navigationBarTitle(Text("Groups").color(.green))
    }
}

文字のフォントと色を変えてみました。
スクリーンショット 2019-06-07 18.56.55.png

画像のハメかた

長くなりそうなので別の記事に書こうと思います。

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

iOS13でpresentViewControllerでの遷移はひとくせあり

iOS13でpresentViewControllerでの遷移はひとくせあり

iOS13のベータ版が公開されたので、早速実機にインストールして、
開発中のアプリをXCode11ベータ版でビルドして動作確認してみました。

そこで、画面遷移の仕様が変わっていました。
具体的にはpresentViewController(セグエも同様でしょう)を使用したモーダルタイプの遷移後に、
前の画面が下に存在することが分かるようなレイヤーチラ見せ方式になっています。
※ナビゲーションのpushの遷移は従来と同様でした。

連絡先アプリの例

iOS13 iOS12
iOS13.PNG iOS12.PNG

これにより、iOS13ではキャンセルボタンを押さずとも
下方向にスワイプすると、今のビューを終了して前の画面に戻ることが可能になりました。

iOS12でも同様の仕様の部分はありました。
例えばミュージックアプリの以下画面など

iOS12_Music.PNG

下スワイプで前の画面に戻れる点では、すごく便利になりますね。
その反面、presentViewControllerで遷移後、
戻ることを想定しない構成で作られていたアプリは、
2019年秋以降のアップデート時には修正が必要になりそうです。

その他にも、仕様の変更点があるようでしたのでそれは別途で。。。
・セグメントなどパーツの見た目の変更
・ダークモードによる背景色・テキストカラーへの影響など

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

TableViewのCell入れ替えでViewが透過されてしまったあなたへ

まくら

iOSアプリといえばUITableViewでございます。たぶん。
今回はそんなUITableViewの小ネタをひとつ。

UITableViewは割と簡単な設定でセルの入れ替えを実現させてくれます。

extension TableWorkViewController: UITableViewDataSource {
    // ...省略...

    // 移動可能
    func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
        return true
    }

    // 移動実行
    func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
        print("source: \(sourceIndexPath)")
        print("dest: \(destinationIndexPath)")
    }
}

こんな感じで設定してやって、どこかで

self.tableView.isEditing = true

してやると、
SS06071401.png

こんな感じで入れ替えアイコンが表示されるようになります。
基本的なものでいいのならこれだけ。簡単。

では実際に入れ替えてみましょう。

SS002.png

お気づきだろうか?

セルの上にUIViewを乗せているのですが、見えなくなっていますね。
どうやら入れ替えの時はセルの上に乗っているUIViewは透明にされてしまっているようです。

何やらちょっと気持ち悪いので、対策を考えてみましょう。

ほんだい

という訳で本題。

自分も地味にハマってしまったのですが、ナイスな力技で解決している方がいたので、マネさせて頂きました。

参考:
https://medium.com/@vhart/tableview-cell-reordering-keep-the-color-4c03623d2399

最高!この力技感!

元ネタの方を見て頂けばわかる通り、移動の時にUIViewbackgroundColorに透明がセットされてしまうので、強制的に色を保持してやろうという訳です。

そんなわけで作ったものがこちら

/// セルの入れ替え時に色をのキープしておくためのView
class TableWorkColorKeepView: UIView {

    /// キープ用のbackgroundColor
    @IBInspectable var keepBackgroundColor: UIColor = .clear {
        didSet {
            self.backgroundColor = self.keepBackgroundColor
        }
    }

    /// 背景色
    override var backgroundColor: UIColor? {
        set {
            // キープしている色と異なればリターン
            guard newValue == keepBackgroundColor else {
                return
            }
            super.backgroundColor = newValue
        }
        get {
            return super.backgroundColor
        }
    }
}

これを使うと、

Simulator Screen Shot - iPhone Xʀ - 2019-06-07 at 18.19.24.png

こうなります。

キープ用の色を用意して、背景色が変更される時にその色と異なったら無視してやっているだけですね。
背景色をセットしたい場合はkeepBackgroundColorの方に設定してやる感じです。
(Storyboardで初期値設定したかったので@IBInspectableしてやっています)

しまいに

力技なので用法用量にご注意を。
もっとスマートなやり方あれば教えてつかあさい。

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

一部の画面だけSwiftUIを使いたいとき

本記事は公開済みのドキュメントを元に作成しています。今後仕様変更などにより記載内容と異なる場合があります。予めご了承ください。

はじめに

SwiftUIを触っていて、
「SwiftUIのViewとUIKitのUIView間の連携ってどうやるんだ?」となったので調べました。
結論から言うと、UIHostingController/UIViewRepresentableでなんとかなりそうです。

UIViewの中で画面の一部として使う

UIView/UIViewControllerの中でSwiftUIのViewを使う場合は、UIHostingControllerでラップしてあげる形になります。
こんな感じで。

import SwiftUI

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        let vc: UIHostingController = UIHostingController(rootView: SubView())
        self.addChild(vc)
        self.view.addSubview(vc.view)
        vc.didMove(toParent: self)

        vc.view.translatesAutoresizingMaskIntoConstraints = false
        vc.view.heightAnchor.constraint(equalToConstant: 320).isActive = true
        vc.view.leftAnchor.constraint(equalTo: self.view.leftAnchor, constant: 16).isActive = true
        vc.view.rightAnchor.constraint(equalTo: self.view.rightAnchor, constant: -16).isActive = true
        vc.view.centerYAnchor.constraint(equalTo: self.view.centerYAnchor).isActive = true
    }
}

struct SubView : View {
    var body: some View {
        Text("にゃーん?")
    }
}

画面遷移先として使う

UIHostingControllerUIViewControllerを継承しているので、そのまま遷移先としても使えます。

import SwiftUI

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        let button: UIButton = UIButton(type: .system)
        button.setTitle("?", for: .normal)
        button.addTarget(self, action: #selector(didTapTransitButton(_:)), for: .touchUpInside)
        self.view.addSubview(button)

        button.translatesAutoresizingMaskIntoConstraints = false
        button.centerYAnchor.constraint(equalTo: self.view.centerYAnchor).isActive = true
        button.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
    }

    @objc func didTapTransitButton(_ sender: UIButton) {
        self.present(UIHostingController(rootView: DetailView()), animated: true)
    }
}

struct DetailView : View {
    var body: some View {
        Text("にゃーん?")
    }
}

Viewの中でUIViewを使う

逆にViewの中でUIViewを使いたい時は、UIViewRepresentableを継承して使います。
チュートリアルのCreating and Combining Viewsにも記載があります。

import SwiftUI

struct ContentView: View {
    var body: some View {
        ZStack {
            BackgroundView()
            Text("にゃーん?")
        }
    }
}

struct BackgroundView: UIViewRepresentable {
    func makeUIView(context: UIViewRepresentableContext<BackgroundView>) -> UIView {
        let view: UIView = UIView()
        view.backgroundColor = UIColor(red: 0.9, green: 0.9, blue: 0.9, alpha: 1.0)
        return view
    }

    func updateUIView(_ uiView: UIView, context: Context) {
        //
    }
}

参考

UIHostingController - SwiftUI | Apple Developer Documentation
UIViewRepresentable - SwiftUI | Apple Developer Documentation
Creating and Combining Views — SwiftUI Tutorials | Apple Developer Documentation

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

無限ループするUIPageViewControllerとタブ

はじめに

ライブラリ「XLPagerTabStrip」を使って横スワイプでタブ・ページ切り替えしていた画面を
無限スクロールさせることになりました。

有名なライブラリだしきっと簡単に無限スクロールできるよね。
と思い調査してみたら、意外とループ機能が無かった...ので自作しました。

自分用の備忘録として、実装のポイントをメモします。

デモ

いろんなアプリでよく見かけますね
スクリーンショット 2019-06-04 8.42.34.png
ページが切り替わるタイミングは3種類。

  • ページを左右スワイプしたとき
  • タブを左右スワイプしたとき
  • 未選択のタブを押下したとき

タブは無限スクロールし、またページの切り替えに合わせて追従します。
選択されたタブは色が変わり、タブのタイトル幅に合わせてバーのサイズも変わります。

環境

  • XCode Version 10.1
  • Swift 4.2

画面構成

タブはCollectionView、ページはPageViewController
タブ下のBarはUIViewで構成しました。
スクリーンショット 2019-06-04 8.42.34.png
CollectionViewにはUILabelが乗ったカスタムCellをセットします。
スクリーンショット 2019-06-04 8.42.34.png

ループ処理

実装するにあたってこちらのサイトにはとてもお世話になりました。
UIPageViewControllerをつかって無限スクロールできるタブUIを実装してOSSとして公開しました

UIPageViewControllerのループ処理

今回のサンプルコードでは画面をA,B,C,Dの4種類用意しました。
スクリーンショット 2019-06-04 8.42.34.png
ページのループ処理は以下の通り。

PageViewController.swift
pageViewController(_ pageViewController: UIPageViewController,  viewControllerBefore viewController: UIViewController)

 pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController)

前後のVCを読み込むDelegateでメソッド「nextViewController」を呼び

PageViewController.swift
    // 画面A,B,C,DのUIViewControllerが格納された配列
    private (set) var pageControllergrop = [UIViewController]()

    private func nextViewController(viewController: UIViewController, isAfter: Bool) -> UIViewController? {
        guard var index = self.pageControllergrop.index(of: viewController) else { return nil }
        index = isAfter ? (index + 1) : (index - 1)

        if index < 0 {
            index = self.pageControllergrop.count - 1
        } else if index == self.pageControllergrop.count {
            index = 0
        }
        if index >= 0 && index < self.pageControllergrop.count {
            return self.pageControllergrop[index]
        }
        return nil
    }

前のVCが無ければ末尾のVCを、後のVCが無ければ頭のVCを表示させることで、ページをループさせます。

UICollectionViewのループ処理

スクリーンショット 2019-06-04 8.42.34.png
高速スクロールしてもCellが見切れないよう、Cell数を項目の5倍用意しました。

PageViewController.swift
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        let isScrollCollectionView: Bool = (scrollView.className == "UICollectionView")
        ...
        // CollectionView スクロール時
        if isScrollCollectionView {
            // CollectionView 1項目分の横幅
            let listWidth = self.collectionView.contentSize.width / 5.0

            // スクロールが左側のしきい値を超えたとき、中央に戻す
            if (self.collectionView.contentOffset.x <= self.cellWidth) {
                self.collectionView.contentOffset.x = (listWidth * 2) + self.cellWidth

            // スクロールが右側のしきい値を超えたとき、中央に戻す
            } else if (self.collectionView.contentOffset.x) >= (listWidth * 3) + cellWidth {
                self.collectionView.contentOffset.x = listWidth + self.cellWidth
            }
        }
        ...
    }

画面左側に1ループ目/1番目のCellが来たら、3ループ目/1番目のCellへ
画面左側に4ループ目/1番目のCellが来たら、2ループ目/1番目のCellへ
強制的にスクロール位置を戻すことでループを実現させます。

ページ切り替えに合わせてタブを追従させる

他アプリでは、ページ切り替え後にタブを移動させるアニメーションを割と見かけましたが
指の動きに合わせて動いてくれた方が見た目タイムラグが無さそうだな
と思ったので実装してみました。

PageViewController.swift
    // スクロール開始位置
    private var startPointPageX:CGFloat = 0

    func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
        // スワイプ前のスクロール位置を保持
        self.startPointPageX = scrollView.contentOffset.x

        // 表示中のページにCellの中心値を合わせるため、CollectionViewの初回スクロール位置を設定
        self.startPointCollectionX = {
            let listWidth = self.collectionView.contentSize.width / CGFloat(self.pagelistsCount)
            let contentOffsetX = listWidth * 2 + (self.cellWidth * CGFloat(self.selectedPageNum))
            let centerMargin = (App.windowWidth - self.cellWidth) / 2
            return contentOffsetX - centerMargin
        }()
        ...
    }

    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        let isScrollCollectionView: Bool = (scrollView.className == "UICollectionView")

        // Pageスクロール中はCollectionViewスクロールを禁止
        self.collectionView.isScrollEnabled = isScrollCollectionView

        if isScrollCollectionView {
            ...
        // PageViewをスクロール時
        } else {
            // スワイプ前の位置からどれだけ移動したか計算
            let changePageX = self.startPointPageX - scrollView.contentOffset.x

            // 1枚のページ内で移動した割合をCellの移動割合に
            let changeCollectionX = self.cellWidth * (changePageX / App.windowWidth)

            // 高速スクロールを封じる
            if abs(changePageX) > 0 && abs(changePageX) < App.windowWidth {
                self.notScrollView.isHidden = false
            }
            // 計算した分CollectionViewをスクロール
            if changeCollectionX != 0 {
                self.collectionView.contentOffset.x = self.startPointCollectionX - changeCollectionX
            }
        }
        ...
    }

これでページをスワイプ中に、指の動きに合わせてCellも追従します。

欠点として、ページを高速で切り替えると、CollectionViewのループ処理と競合して
CollectionViewの追従がズレてしまう問題が発生しました。

追従時のみUIPageViewControllerの上に透明なUIViewを表示することで
ページの高速スクロールを封じることができましたが、この辺りもっとイケてる実装にできないか..。

タブの色味を追従に合わせる

画面中央に来たCellのタイトルを青く染めます。またタブ下のバーの横幅を変更します。
画面が切り替わったら色が変わるのではなく、中央に来たら変わるのがポイント。

PageViewController.swift
    private let pagelist: [String] = ["A", "BBBB", "CCC", "DDDDDD"]
    private var selectedCollectionNum:Int!

    // Cellの色を更新するメソッド
    private func changeCellColor(_ indexPath: IndexPath? = nil) {

        // 一旦全てのCell色をリセット
        for cell in self.collectionView.visibleCells {
            if let cell = cell as? BarCollectionViewCell {
                // カスタムCell内に配置しているLabelを灰色に
                cell.setSelectedCell(false)
            }
        }
        // 更新するCellが存在しなければreturn
        guard let indexPath = indexPath else {
            self.selectedCollectionNum = nil
            return
        }
        guard let cell = self.collectionView.cellForItem(at: indexPath) as? BarCollectionViewCell else {
            self.selectedCollectionNum = nil
            return
        }
        // 選択したCell番号を保持
        self.selectedCollectionNum = indexPath.row
        // バーの横幅を更新
        self.changeSelectBar(indexPath.row % self.pagelist.count)
        // カスタムCell内に配置しているLabelを青色に
        cell.setSelectedCell(true)
    }

    private func changeSelectBar(_ nextPageNum: NSInteger) {
        // バーの横幅を、タイトルの文字数に合わせて計算
        let titleWidth: CGFloat = (CGFloat(self.pagelist[nextPageNum].count) * 15) + 14

        UIView.animate(
            withDuration: 0.25,
            animations: {
                self.selectBarView.frame.size = CGSize(width: titleWidth,  height: 3)
                self.selectBarView.center.x = App.windowWidth / 2
        })
    }
BarCollectionViewCell.swift
class BarCollectionViewCell: UICollectionViewCell {

    @IBOutlet private weak var categoryTitleLabel: UILabel!

    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
    }
    func setTitle(_ title: String) {
        self.categoryTitleLabel.text = title
        setSelectedCell(false)
    }
    // ラベルの色を切り替え
    func setSelectedCell(_ select: Bool) {
        self.categoryTitleLabel.textColor = select ? UIColor.blue : UIColor.gray
    }
}

selectBarViewは、タブ下に配置しているUIViewです。
本メソッドは、ページスクロール前、スクロール中、スクロール後、タブ選択時に呼ばれます。
具体的には以下の箇所。

  • scrollViewDidScroll
  • scrollViewDidEndDragging
  • scrollViewDidEndDecelerating
  • scrollViewWillBeginDragging // 全Cellの色味をリセットするため
  • collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)

選択したCell番号を、selectedCollectionNumに保持している理由は
scrollViewDidScrollで本メソッドが何度も無駄に呼ばれるのを防ぐため。

PageViewController.swift
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        ...
        // cellの色味を更新
        let center = self.view.convert(self.collectionView.center, to: self.collectionView)
        guard let indexPath = self.collectionView.indexPathForItem(at: center) else { return }
        if self.selectedCollectionNum != indexPath.row {
            self.changeCellColor(indexPath)
        }
    }

scrollViewDidScroll内で中央のCellを算出後、CellのindexPath.rowとselectedCollectionNumを比較し
異なっている時だけメソッドを呼んでいます。

まとめ

実装の詳細は、Githubのサンプルコードからどうぞ。
LoopPageDemo

ページの高速スクロールはできませんが、ライブラリ不使用でそれなりのUIになりました。
もっとこうした方が良い等アドバイスがあれば、ぜひ (#・ω・)

参考サイト

UIPageViewControllerをつかって無限スクロールできるタブUIを実装してOSSとして公開しました

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

SwiftUIのTabbarで遊んでみる

SwiftUIをさわり始めて数日ですが、今までのやり方で「どうやってやるの?」的なことが多くあると思いますが、今回扱うTabbarも色々変わっっているので軽くまとめます。

あと、Viewの背景色変更する方法を暫定ですがいい感じにできたのでそこも紹介します。

※スクショは自粛で〜

First Code

Tabbarでの構成でプロジェクトを作成するとこんな感じにContentViewを作成してくれます。

まずはこれだけを見ていきましょう?

struct ContentView : View {
    @State private var selection = 0

    var body: some View {
        TabbedView(selection: $selection){
            Text("First View")
                .font(.title)
                .tabItemLabel(Image("first"))
                .tag(0)
            Text("Second View")
                .font(.title)
                .tabItemLabel(Image("second"))
                .tag(1)
        }
    }
}

SetTabItem

タブバーのアイテムを設定してあげています
画像なりテキストなり

func tabItemLabel<V>(V) -> _ModifiedContent<TabbedView<SelectionValue, Content>, _TraitWritingModifier<AnyView?>>

Sets the tab item to be used for this content.

Configuring Switch View

要調査ですがタグを振ってあげないとうまく切り替わってくれないです

func tag<V>(V) -> _AutoResultView<TabbedView<SelectionValue, Content>>

Viewの切り替え

ContentView.swift

        TabbedView(selection: $selection) {
            FirstView()
                .tabItemLabel(Image("first"))
                .tag(0)

            SecondView()
                .tabItemLabel(Image("second"))
                .tag(1)
            }
            .onAppear { print(onAppear) }


FirstView

struct FirstView : View {
    var body: some View {
            Text("First")
    }
}

SecondView

struct SecondView : View {
    var body: some View {
            Text(Second")
    }
}

Append self view background color

View全体に背景色を追加する方法ですね
正しいやり方か分からんけど、

ZStackを使うのはわかったけどSpacerの挙動がイマイチ理解できない、、

    var body: some View {

        ZStack {
            Spacer()
                .position(.zero)
            Text("First")
                .color(Color.white)
            }.background(Color.red)
            .edgesIgnoringSafeArea(.all)
    }

タブバーで表示されるViewを画面全体に表示

.edgesIgnoringSafeArea(.all)だとタブバー見えなくなちゃったので上だけで

struct ContentView : View {
    @State private var selection = 0

    var body: some View {
        TabbedView(selection: $selection) {
            FirstView()
                .tabItemLabel(Image("first"))
                .tag(0)

            SecondView()
                .tabItemLabel(Image("second"))
                .tag(1)
            }
            .onAppear { print("onAppear") }
            .edgesIgnoringSafeArea(.top)
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SwiftUIのTabbedViewで遊んでみる

SwiftUIをさわり始めて数日ですが、今までのやり方で「どうやってやるの?」的なことが多くあると思いますが、今回扱うTabbarも色々変わっっているので軽くまとめます。

あと、Viewの背景色変更する方法を暫定ですがいい感じにできたのでそこも紹介します。

※スクショは自粛で〜

First Code

Tabbarでの構成でプロジェクトを作成するとこんな感じにContentViewを作成してくれます。

まずはこれだけを見ていきましょう?

struct ContentView : View {
    @State private var selection = 0

    var body: some View {
        TabbedView(selection: $selection){
            Text("First View")
                .font(.title)
                .tabItemLabel(Image("first"))
                .tag(0)
            Text("Second View")
                .font(.title)
                .tabItemLabel(Image("second"))
                .tag(1)
        }
    }
}

SetTabItem

タブバーのアイテムを設定してあげています
画像なりテキストなり

func tabItemLabel<V>(V) -> _ModifiedContent<TabbedView<SelectionValue, Content>, _TraitWritingModifier<AnyView?>>

Sets the tab item to be used for this content.

Configuring Switch View

要調査ですがタグを振ってあげないとうまく切り替わってくれないです

func tag<V>(V) -> _AutoResultView<TabbedView<SelectionValue, Content>>

Viewの切り替え

ContentView.swift

        TabbedView(selection: $selection) {
            FirstView()
                .tabItemLabel(Image("first"))
                .tag(0)

            SecondView()
                .tabItemLabel(Image("second"))
                .tag(1)
            }
            .onAppear { print(onAppear) }


FirstView

struct FirstView : View {
    var body: some View {
            Text("First")
    }
}

SecondView

struct SecondView : View {
    var body: some View {
            Text(Second")
    }
}

Append self view background color

View全体に背景色を追加する方法ですね
正しいやり方か分からんけど、

ZStackを使うのはわかったけどSpacerの挙動がイマイチ理解できない、、

    var body: some View {

        ZStack {
            Spacer()
                .position(.zero)
            Text("First")
                .color(Color.white)
            }.background(Color.red)
            .edgesIgnoringSafeArea(.all)
    }

タブバーで表示されるViewを画面全体に表示

.edgesIgnoringSafeArea(.all)だとタブバー見えなくなちゃったので上だけで

struct ContentView : View {
    @State private var selection = 0

    var body: some View {
        TabbedView(selection: $selection) {
            FirstView()
                .tabItemLabel(Image("first"))
                .tag(0)

            SecondView()
                .tabItemLabel(Image("second"))
                .tag(1)
            }
            .onAppear { print("onAppear") }
            .edgesIgnoringSafeArea(.top)
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【iOS】対応必須かも?Sign In with Appleまとめ(第一報)

Sign In with Appleとは?

今年のWWDCで発表された
Appleアカウントでアプリの
サインアップやサインインができる機能です。

スクリーンショット 2019-06-07 8.38.08.png

対応必須?

様々なニュースで下記のようなことが書かれており
https://japan.cnet.com/article/35138002/

レビューガイドラインを見る限り
サードパーティーのログイン連携をしているアプリは
Sign In with Appleの実装が必須になるようです。

弊社のアプリでもFacebookログインなどを使用しており
今後対応が必要になるだろうと思い
まずどういうものか知るために
サンプルコードとセッション動画の内容から
現状わかったことをまとめてみました。

公式のサイトはこちら
https://developer.apple.com/sign-in-with-apple/

レビューガイドラインの更新情報はこちら
https://developer.apple.com/news/?id=06032019j

Introducing Sign In with Apple(セッション動画)
https://developer.apple.com/videos/play/wwdc2019/706/

サンプルコード
https://developer.apple.com/documentation/authenticationservices/adding_the_sign_in_with_apple_flow_to_your_app

ちなみに
ベータテストは今夏に
正式版は今年の終わり近くに利用可能になる予定のようです。

Sign In with Apple will be available for beta testing this summer. 
It will be required as an option for users in apps that 
support third-party sign-in when it is commercially available later this year.

※ 
Xcodeの画面などはNDAの関係があるため
正式版になったあとに追加させていただきます:bow_tone1:

使用しているものは
公開されているスライド、ドキュメントやサンプルコードを使用していますが
もし不適切な箇所がございましたら
ご指摘いただけますと幸いです:bow_tone1:

Sign In with Appleの特徴

スクリーンショット 2019-06-07 5.48.55.png

早く、簡単にアプリへの
サインアップやサインインができるとのことです。

主な特徴として下記の5つの点が挙げられていました。

スクリーンショット 2019-06-07 5.47.37.png

合理化されたアカウントセットアップ

すでにAppleのアカウントへは
ログイン状態にあることが多いので
タップだけでセットアップが完了できます。

また
デバイスをまたがって同じ認証情報が使用可能です。

認証されたメールアドレス

Appleがすでに認証しているので
メールを受け取って->リンクをタップする
などの余計な認証プロセスが必要になることはありません。

Hide My Email

スクリーンショット 2019-06-07 9.07.11.png

スクリーンショット 2019-06-07 9.06.00.png

実際のメールアドレスとは異なる
下記のようなメールアドレスを使うことができ
アプリに個人情報を渡すことを避けることができます。

もちろん本物のメールアドレスと同じように使えます。

※ Appleがメール内容を保持するということはないとも言っていました。

スクリーンショット 2019-06-07 8.38.27.png

ビルトインのセキュリティ

パスワードを忘れたとしても
Appleアカウントでは2段階認証を使っているため
追加のパスワード認証プロセスや入力が必要ありません。

Anti-Fraud(詐欺対策)

ユーザが疑わしいかどうかをシステムがサポートしてくれます。

内部的な情報とアカウント情報を照らし合わせて判断しているようです。

スクリーンショット 2019-06-07 8.38.37.png

マルチプラットフォーム

iOS macOS iPadOS watchOS Javascript
実装することが可能です。

処理の流れ

下記の様な手順で行っていきます。

スクリーンショット 2019-06-07 5.47.59.png

具体的にどのような処理を行っていくか
ここからはサンプルのコードで見ていきたいと思います。

Sign in with Appleを使用するためには
AuthenticationServicesというフレームワークを用います。

https://developer.apple.com/documentation/authenticationservices

これを使用することで

  • AppleIDを用いてログインをすることができる
  • サインインフローの中で保存しているパスワードを使用することができる
  • OAuthのような仕組みを使用してWebブラウザとアプリ間でデータを共有することができる
  • エンタープライズアプリではシングルサインオン(SSO)のような体験を提供できる

などが実現できます。

OAuthとは?
https://ja.wikipedia.org/wiki/OAuth

シングルサインオンとは?
https://ja.wikipedia.org/wiki/%E3%82%B7%E3%83%B3%E3%82%B0%E3%83%AB%E3%82%B5%E3%82%A4%E3%83%B3%E3%82%AA%E3%83%B3

では実際の中身に関してはサンプルのコードから見ていきたいと思います。

Xcodeの設定をする(Sign In with Apple Entitlementを用意する)

まずはXcodeの設定が必要です。
targetを選択しSigning&Capabilitiesの中から
Sing in With Appleを選択します。

https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_developer_applesignin

サインアップボタンを用意する

ASAuthorizationAppleIDButtonを使用します。

標準のものをそのまま使用することも可能です。

カスタマイズも可能ですが、
その際にはデザインガイドラインに従う必要があります。

デザインガイドラインはこちら
https://developer.apple.com/design/human-interface-guidelines/sign-in-with-apple/overview/

ASAuthorizationAppleIDButton
https://developer.apple.com/documentation/authenticationservices/asauthorizationappleidbutton

Appleにユーザ情報を取得するためのリクエストを送る

次にユーザの情報を取得するために
Appleへリクエストを送ります。

ユーザにタップされた際の処理は下記のようになります。

@objc
func handleAuthorizationAppleIDButtonPress() {
    let appleIDProvider = ASAuthorizationAppleIDProvider()
    let request = appleIDProvider.createRequest()
    request.requestedScopes = [.fullName, .email]

    let authorizationController = ASAuthorizationController(authorizationRequests: [request])
    authorizationController.delegate = self
    authorizationController.presentationContextProvider = self
    authorizationController.performRequests()
}

ここで出てくるいくつかのクラスについて見ていきます。

ASAuthorizationAppleIDProvider

ASAuthorizationAppleIDProvider
ASAuthorizationAppleIDRequestを用いて
ユーザのAppleIDを元に
アプリが認証済みかどうかのリクエストを作成します。

let appleIDProvider = ASAuthorizationAppleIDProvider()
let request = appleIDProvider.createRequest()
request.requestedScopes = [.fullName, .email]

fullNameemailは取得したい情報を指定することができ
オプションで指定することができます。

ASAuthorizationAppleIDProvider
https://developer.apple.com/documentation/authenticationservices/asauthorizationappleidprovider

ASAuthorizationAppleIDRequest
https://developer.apple.com/documentation/authenticationservices/asauthorizationappleidrequest

ASAuthorizationController

次に上記で作成したリクエストを使って
ASAuthorizationControllerを初期化し
performRequestsでAppleへリクエストを送ります。

let controller = ASAuthorizationController(authorizationRequests: [request])
authorizationController.delegate = self
authorizationController.presentationContextProvider = self
authorizationController.performRequests()

ASAuthorizationController
https://developer.apple.com/documentation/authenticationservices/asauthorizationcontroller

performRequests
https://developer.apple.com/documentation/authenticationservices/asauthorizationcontroller/3153047-performrequests

ASAuthorizationControllerDelegate

上記の中で

authorizationController.delegate = self

とありますが

これはASAuthorizationControllerDelegateというプロトコルです。

ASAuthorizationControllerDelegate
https://developer.apple.com/documentation/authenticationservices/asauthorizationcontrollerdelegate

これを設定するとリクエストのレスポンスを下記のデリゲートで取得することができます。

extension LoginViewController: ASAuthorizationControllerDelegate {
    func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {

        // ASAuthorizationAppleIDCredentialの場合
        if let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential {

            // 取得できた情報
            let userIdentifier = appleIDCredential.user
            let fullName = appleIDCredential.fullName
            let email = appleIDCredential.email

            // 取得した情報を元にアカウントの作成などを行う

        // ASPasswordCredentialの場合(※あとで紹介します※)
        } else if let passwordCredential = authorization.credential as? ASPasswordCredential {
            // 既存のiCloud Keychainクレデンシャル情報
            let username = passwordCredential.user
            let password = passwordCredential.password

            // 取得した情報を元にアカウントの作成などを行う

        }
    }    
}

ASAuthorizationAppleIDProviderのリクエストが成功した場合
取得できる情報はASAuthorizationAppleIDCredentialです。

ASAuthorizationAppleIDCredential
https://developer.apple.com/documentation/authenticationservices/asauthorization

この中の情報を使ってアカウントの作成などを行います。

authorizationController(controller:didCompleteWithAuthorization:)
https://developer.apple.com/documentation/authenticationservices/asauthorizationcontrollerdelegate/3153050-authorizationcontroller

エラーの場合は下記のデリゲートメソッドが呼ばれます。

func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
    // Handle error.
}

authorizationController(controller:didCompleteWithError:)
https://developer.apple.com/documentation/authenticationservices/asauthorizationcontrollerdelegate/3153051-authorizationcontroller

ASPasswordCredentialについて

上記のデリゲートメソッドの中で

} else if let passwordCredential = authorization.credential as? ASPasswordCredential {

という分岐が出てきていますが
これはiCloud KeyChainのパスワード情報をリクエストした場合に
取得できる情報です。

ASPasswordCredential
https://developer.apple.com/documentation/authenticationservices/aspasswordcredential

実はサンプルコードの中では
ユーザのタップする場合以外にも
viewDidAppearの中でも
認証情報のリクエストを送っています。

func performExistingAccountSetupFlows() {
    // Prepare requests for both Apple ID and password providers.
    let requests = [ASAuthorizationAppleIDProvider().createRequest(),
                    ASAuthorizationPasswordProvider().createRequest()]

    // Create an authorization controller with the given requests.
    let authorizationController = ASAuthorizationController(authorizationRequests: requests)
    authorizationController.delegate = self
    authorizationController.presentationContextProvider = self
    authorizationController.performRequests()
}

注目点は下記で

let requests = [ASAuthorizationAppleIDProvider().createRequest(),
                ASAuthorizationPasswordProvider().createRequest()]

ASAuthorizationPasswordProviderを使っています。

ASAuthorizationPasswordProvider
https://developer.apple.com/documentation/authenticationservices/asauthorizationpasswordprovider

このリクエストを送ることで
iCloud Keychainのクレデンシャル情報を使って
アカウントの作成などが可能になります。

※複数設定した場合の優先順位などに関しては何も言及がありませんでしたので
これは今後調べたいと思います。

今回は割愛させていただきますが
シングルサインオン用の入り口もあります。

ASAuthorizationSingleSignOnProvider
https://developer.apple.com/documentation/authenticationservices/asauthorizationsinglesignonprovider

ASAuthorizationSingleSignOnCredential
https://developer.apple.com/documentation/authenticationservices/asauthorizationsinglesignoncredential

ASAuthorizationControllerPresentationContextProviding

リクエストを送る際に下記のコードがありました。

authorizationController.presentationContextProvider = self

これはASAuthorizationControllerPresentationContextProvidingプロトコルで
システムが認証プロセスをユーザに提供するためのUIWindowの指定をします。
いくつかのリクエストでは必須となっているようです。

ASAuthorizationControllerPresentationContextProviding
https://developer.apple.com/documentation/authenticationservices/asauthorizationcontrollerpresentationcontextproviding

サンプルコードでは
下記のようにアプリのUIWindowを設定しています。

extension LoginViewController: ASAuthorizationControllerPresentationContextProviding {
    func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
        return self.view.window!
    }
}

presentationAnchor(for:)
https://developer.apple.com/documentation/authenticationservices/asauthorizationcontrollerpresentationcontextproviding/3237228-presentationanchor

このようにしてユーザのアカウント作成ができます。

クレデンシャル情報の変更に対応する

例えば

  • アプリでAppleIDの使用をやめた
  • デバイスでサインアウトをした

などが起きた場合への対応も必要になります。

サンプルのAppDelegateを見ていきます。

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    let appleIDProvider = ASAuthorizationAppleIDProvider()
    appleIDProvider.getCredentialState(forUserID: KeychainItem.currentUserIdentifier) { (credentialState, error) in
        switch credentialState {
        case .authorized:
            // Apple IDクレデンシャルが妥当だった場合
            break
        case .revoked:
            // Apple IDクレデンシャルが無効だった場合
            break
        case .notFound:
            // Apple IDクレデンシャル情報がなかった場合

            // サインアップのフローを開始する

        default:
            break
        }
    }
    return true
}

発表のスライドとはやや異なりますが、
Keychainに保存しているcurrentUserIdentifier
getCredentialState(forUserID:completion:)の引数として設定し
クレデンシャル情報を取得しています。

getCredentialState(forUserID:completion:)
https://developer.apple.com/documentation/authenticationservices/asauthorizationappleidprovider/3175423-getcredentialstate

ここで得られるcredentialState
ASAuthorizationAppleIDProvider.CredentialStateです。

enum CredentialState : Int {
    case authorized = 1
    case notFound = 2
    case revoked = 3
}

ASAuthorizationAppleIDProvider.CredentialState
https://developer.apple.com/documentation/authenticationservices/asauthorizationappleidprovider/credentialstate

このrevokedの際に対応していきます。

また発表スライドでは
通知を受け取ることもできるとあり
下記のような形で
クレデンシャル情報が無効になった時の処理ができます。

// Register for revocation notification
let center = NotificationCenter.default
let name = NSNotification.Name.ASAuthorizationAppleIDProviderCredentialRevoked
let observer = center.addObserver(forName: name, object: nil, queue: nil) { (Notification) in
 // サインアウトして、再度サインインフローを表示するなど
}

ASAuthorizationAppleIDProviderCredentialRevoked
https://developer.apple.com/documentation/foundation/nsnotification/name/3175424-asauthorizationappleidprovidercr

サインイン後はワンタップでサインインできる

サインアップのプロセスを見てきましたが
Appleアカウントでアプリの認証がされていることで
アプリ起動時に認証されているかどうかがわかり
即座にサインイン状態だと判定することが可能です。
※ 上記AppDelegateのメソッド内の分岐をご参照ください。

これはユーザにとっても
不要な手順が省かれ楽になり
個人情報を入力するリスクを避けられます。

最後に

サンプルコードとセッションの情報から
まずまとめてみました。

まだまだ理解ができていない部分や知らないことは
多々あると思いますので
更新情報がありましたら追記したいと思います。

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

iOS13のScene-Basedライフサイクル(UISceneDelegate)

はじめに

iOS12のアプリライフサイクル(AppDelegate)を調べていたら、
iOS13のscene-basedだとライフサイクルが変わるようなので、
資料から読み取れる概要を書いていきます。
iOS12のAppDelegate相当が、 scene-basedの場合には、
UISceneDelegateになるようです。

Scene-Basedライフサイクル(ios13以降)

大きな考え方は、ios12のApp-Basedライフサイクルとそれほど変わらなさそう。

フォアグランドに遷移する時には、以下のメソッドが呼ばれる。
- func sceneWillEnterForeground(UIScene)
- func sceneDidBecomeActive(UIScene)

バックグランドに遷移する時には、以下のメソッドが呼ばれる。
- func sceneWillResignActive(UIScene)
- func sceneDidEnterBackground(UIScene)

20190607-ios13-lifecycle.png

App-Basedライフサイクル(参考)

20190606-ios-lifecycle.png

参考文献

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