【Swift】UserDefaultにタプルを辞書で保存する

どうも、ねこきち(@nekokichi1_yos2)です。

 

タプルをUserDefaultに保存する方法を模索してて、良い方法を見つけたので、備忘録として残します。

 

解説

 

UserDefaultが保存できるのは、

  • Int
  • String
  • Array
  • Dictionary
  • Double
  • Float
  • Bool
  • URL

なので、タプル(Tuple)は保存できない。

 

したがって、タプル→対応している型、に変換する必要がある。

 

辞書型(Dictionary)は、タプルと同様に、キー(もしくはラベル)で値を参照でき、見た目でも似てるので、辞書の方が都合が良い。

 

Data型で保存できないのか?

 

下記のコードで、Data型に変換を試みました。

let tuple = (text:"fdsafs", bool:true)
let data:Data = try! NSKeyedArchiver.archivedData(withRootObject: tuple, requiringSecureCoding: false)
UserDefaults.standard.set(data, forKey: "data")

 

しかし、下記のエラーが発生。

Fatal error: 'try!' expression unexpectedly raised an error: Error Domain=NSCocoaErrorDomain Code=4866 "Caught exception during archival: -[__SwiftValue encodeWithCoder:]: unrecognized selector sent to instance 0x600002558880

 

詳しく言うと、

なので、

  • 他の型を使用する
  • Codableを使用する

で実装した方がいいらしい。

 

(引用元)

stackoverflow.com

stackoverflow.com

 

タプル→辞書、に変換

 

注意点として、タプルは必ずラベルを記述する必要があり、辞書型のように[String : Any]と記述することはできない。

 

なので、

  • 保存用のタプルを宣言
  • typealiasでタプルを別名で宣言

のいずれかを実装するしかない。

 

(typealiasについては下記を参照)

qiita.com

 

    //タプル -> 辞書に変換
    func convertToDictionaryFromTuple(tuple: (text:String, num:Int)) -> [String: Any] {
        return [
            "num"      : tuple.num,
            "text"     : tuple.text
        ]
    }
textListForUD.append(convertToDictionaryFromTuple(tuple: textList[i]))

 

辞書→タプル、に変換

 

返り値にタプルを指定する際には、ラベルを定義する必要があり。

 

    //辞書 -> タプルに変換
    func convertToTupleFromDictionary(dictionary: [String: Any]) -> (text:String, num:Int) {
        return (
            num     : dictionary["num"]  as! Int,
            text    : dictionary["text"] as! String
        )
    }
textList.append(convertToTupleFromDictionary(dictionary: checkListData[i]))

 

結果

 

f:id:nekokichi_yos2:20200825105558g:plain

 

ソースコード

 

f:id:nekokichi_yos2:20200825103831p:plain

 

f:id:nekokichi_yos2:20200825110940p:plain

 

import UIKit

class TupleAndDictionaryViewController: UIViewController,UITableViewDelegate,UITableViewDataSource {
    
    @IBOutlet weak var tableView: UITableView!
    
    private let ud = UserDefaults.standard
    //リスト(タプルの配列)
    private var textList:Array<(text:String, num:Int)> = []
    
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        //UserDefaultからデータを取得
        if let checkListData = ud.object(forKey: "checkList") as? [[String: Any]] {
            for i in 0..<checkListData.count {
                textList.append(convertToTupleFromDictionary(dictionary: checkListData[i]))
            }
        }

        //tableViewの設定
        tableView.delegate   = self
        tableView.dataSource = self
        tableView.register(UINib(nibName: "CustomCell", bundle: nil), forCellReuseIdentifier: "customcell")
    }
    
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return textList.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        //CustomCellを生成
        let cell = tableView.dequeueReusableCell(withIdentifier: "customcell", for: indexPath) as! CustomCell
        //CustomCell内のUIに値を入れる
        cell.configure(text: textList[indexPath.row].text, num: textList[indexPath.row].num)
        return cell
    }
    
    
    //タプル -> 辞書に変換
    func convertToDictionaryFromTuple(tuple: (text:String, num:Int)) -> [String: Any] {
        return [
            "num"      : tuple.num,
            "text"     : tuple.text
        ]
    }
    
    //辞書 -> タプルに変換
    func convertToTupleFromDictionary(dictionary: [String: Any]) -> (text:String, num:Int) {
        return (
            num     : dictionary["num"]  as! Int,
            text    : dictionary["text"] as! String
        )
    }
    
    
    @IBAction func segueToAddItemVC(_ sender: Any) {
        performSegue(withIdentifier: "add", sender: nil)
    }
    
    @IBAction func unwindToVC(_ unwindSegue: UIStoryboardSegue) {
        //AddCheckItemで追加ボタンが押下された時
        if unwindSegue.identifier == "addItem" {
            //新規データを追加
            let addCheckItemVC = unwindSegue.source as! AddItemViewController
            textList.append(addCheckItemVC.textItem)
            
            //UserDefaultに保存
            var textListForUD = [[String: Any]]()
            for i in 0..<textList.count {
                textListForUD.append(convertToDictionaryFromTuple(tuple: textList[i]))
            }
            ud.set(textListForUD, forKey: "checkList")
            //初期化
            textListForUD = [[String: Any]]()
            
            //tableViewを更新
            tableView.reloadData()
        }
    }
    
}

class AddItemViewController: UIViewController { @IBOutlet private weak var itemTextField: UITextField! @IBOutlet private weak var itemNumField: UITextField! private(set) var textItem:(text:String, num:Int)! func showAlert(title:String, message:String) { let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert) let cancelAction = UIAlertAction(title: "OK", style: .cancel, handler: nil) alertController.addAction(cancelAction) present(alertController, animated: true, completion: nil) } @IBAction func addItem(_ sender: Any) { if itemTextField.text == "" || itemNumField.text == "" { showAlert(title: "エラー", message: "1文字以上の文字を入力してください") } else if Int(itemNumField.text!) == nil { showAlert(title: "エラー", message: "数字を入力してください") } else { let itemText = itemTextField.text! let itemNum = Int(itemNumField.text!) ?? 0 //入力値を追加用のデータに代入 textItem = (text:itemText, num:itemNum) //ViewControllerに戻る performSegue(withIdentifier: "addItem", sender: nil) } } }

 

import UIKit

class CustomCell: UITableViewCell {

    @IBOutlet private weak var cellNumLabel: UILabel!
    @IBOutlet private weak var cellTextLabel: UILabel!
    
    func configure(text:String, num:Int) {
        cellNumLabel.text  = String(num)
        cellTextLabel.text = text
    }
    
}

 

参考

 

qiita.com

qiita.com

【Swift】unWindSegueで前画面に戻りつつ値を渡す

どうも、ねこきち(@nekokichi1_yos2)です。

 

今回は、unWindSegue、の使用方法を書いていきます。 

 

解説 

 

手順は、

  1. 画面を用意する(2個以上)
  2. 前画面にunWindSegueを記述
  3. StoryBoardで遷移元にunWindSegueを接続
  4. 遷移元でunWindSegueを実行 

 

画面を用意する(2個以上)

 

今回の画面構成は下記。

  • 前画面:unWindSegue1
  • 遷移元:unWindViewController

 

f:id:nekokichi_yos2:20200809121124p:plain

 

前画面にunWindSegueを記述

 

前画面(unWindSeuge1)にunWindSegueを記述します。

(@IBActionと書かれてますが、UI部品と接続することはありません。)

 

また、引数のunwindSegueには、下記のプロパティが用意されています。

 

今回の場合、遷移元がunWindViewControllerなので、unWindSegue.source、にはunWindViewControllerが格納されます。

 

そして、遷移元の変数に直接、アクセスできます。

(下記では、遷移先のtextFieldの値をlabelに代入しています。)

 

    @IBOutlet weak var label: UILabel!
    
    @IBAction func unwind(_ unwindSegue: UIStoryboardSegue) {
        //unWindViewController
        guard let source = unwindSegue.source as? unWindViewController else { return }
        label.text = source.textField.text
    }

 

StoryBoardで遷移元にunWindSegueを接続

 

前提として、遷移先にunWindSegueを記述する必要があります。

(でないと、接続できません。)

 

StoryBoard上で、unWindViewControllerをExitに接続します。

 

f:id:nekokichi_yos2:20200809122821p:plain

 

先ほど、遷移先で作ったunWindSegueを選択します。

 

f:id:nekokichi_yos2:20200809122825p:plain

 

次は、unWindSegueを選択し、

 

f:id:nekokichi_yos2:20200809122512p:plain

 

Identifierを設定します。

(今回はexit)

 

f:id:nekokichi_yos2:20200809122336p:plain

 

遷移元でunWindSegueを実行 

 

あとは、遷移元でperformSegueを実行するだけです。

(withIdentifierには、StoryBoard上で設定したIdentifierを記述します。)

 

    @IBAction func back(_ sender: Any) {
        performSegue(withIdentifier: "exit", sender: nil)
    }

 

結果

 

f:id:nekokichi_yos2:20200809123534g:plain

 

ソースコード

 

import UIKit

class unWindSegue1: UIViewController {
    
    @IBOutlet weak var label: UILabel!
    
    @IBAction func unwind(_ unwindSegue: UIStoryboardSegue) {
        //unWindViewController
        guard let source = unwindSegue.source as? unWindViewController else { return }
        label.text = source.textField.text
    }

}

 

import UIKit

class unWindViewController: UIViewController {
    
    @IBOutlet weak var textField: UITextField!
    
    @IBAction func back(_ sender: Any) {
        performSegue(withIdentifier: "exit", sender: nil)
    }

}

 

参考

 

[Xcodeアプリ開発入門] Part32 Modal遷移時にデータを前の画面に戻す方法

youtu.be

 

【Swift】UserDefaultに非対応のデータを保存する

どうも、ねこきち(@nekokichi1_yos2)です。

 

今回は、UserDefaultでAny型に含まれる、UserDefaultには非対応のデータを扱う方法を書いていきます。

 

解説

 

Any型で保存できないの?

 

一部のデータは保存できました。

 

例えば、普通の辞書や配列は問題ありませんでした。

// [String]
UserDefaults.standard.set(["gsgs","gs","56252"], forKey: "array")
// [String : Array]
UserDefaults.standard.set(["color":[2,3,4], "afa":[2,5,2]], forKey: "array")
// [String : [String : Int]]
UserDefaults.standard.set(["dic1":["2":2],"dic2":["1":1]], forKey: "array")

 

しかし、UILabelの配列や、異なる型を格納した辞書だと、エラーが出ます。

let dic:[String:Any] = [
    "color":UIColor.red,
    "bool":bool
]
UserDefaults.standard.set(dic, forKey: "color_bool")

 

Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Attempt to insert non-property list object {

    bool = 1;

    color = "UIExtendedSRGBColorSpace 1 0 0 1";

} for key color_bool'

 

残念ながら、Any型でも、あらゆるデータを保存することはできないようです。

 

じゃあどうするか、Data型に変換すれば良いのです。

 

Data型に変換して、Data型から元の型に戻す

 

Data型への変換は、NSKeyedArchiver.archivedData、を使用します。

(requiringSecureCodingの値は、true,false、のどちらでも問題ありません。)

let data:Data = try! NSKeyedArchiver.archivedData(withRootObject: 保存したいデータ, requiringSecureCoding: false)

 

そして、保存したデータを取り出す際は、NSKeyedArchiver.unarchiveTopLevelObjectWithData、で取り出します。

(変数に入れる場合、型を指定する必要があります。)

if let data = UserDefaults.standard.data(forKey: "キー")  {
    let dic = try! NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as! [String:Any]
let text = dic["bool"] as! Bool
}

 

結果

 

f:id:nekokichi_yos2:20200801101922g:plain


 

 

ソースコード

 

import UIKit

class ViewController2: UIViewController {
    
    @IBOutlet weak var label: UILabel!
    
    @IBAction func display(_ sender: Any) {
        if let data = UserDefaults.standard.data(forKey: "color_bool")  {
            let dic = try! NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as! [String:Any]
            let text = dic["bool"] as! Bool
            label.text = "\(text)"
            label.backgroundColor = dic["color"] as! UIColor
        }
    }
    
    @IBAction func red(_ sender: Any) {
        setData(.red, true)
    }
    
    @IBAction func blue(_ sender: Any) {
        setData(.systemBlue, false)
    }
    
    @IBAction func yellow(_ sender: Any) {
        setData(.yellow, true)
    }
    
    func setData(_ color:UIColor, _ bool:Bool) {
        let dic:[String:Any] = [
            "color":color,
            "bool":bool
        ]
        let data:Data = try! NSKeyedArchiver.archivedData(withRootObject: dic, requiringSecureCoding: false)
        UserDefaults.standard.set(dic, forKey: "color_bool")
    }
    
}

 

参考

 

 

stackoverflow.com

qiita.co

stackoverflow.com

 

 

【Swift】GeccoでスポットライトなUIを実現

どうも、ねこきち(@nekokichi1_yos2)です。

 

アプリの初回起動時、チュートリアルが流す場合、ユーザーにUIの説明をする必要があります。

 

その際、スポットライトでUIを照らして、テキストで説明するチュートリアルをたまに見かけます。

 

f:id:nekokichi_yos2:20200731112118p:plain
(参考:https://www.mobile-patterns.com/coach-marks

 

そこで今回は、スポットライトを表示させるライブラリ - Gecco - の実装方法を書いていきます。

 

github.com

 

 

注意

 

Geccoは4,5年前にGitHubでの更新が止まっているので、今の環境で動くかどうか保証はできません。

 

本記事を投稿した時点では問題なくビルドできましたが、今後のSwiftやXcodeのバージョンアップでエラーが起こる可能性があるかもしれないことにご注意を。

 

解説

 

手順は、下記の4つ。

  1. Geccoを導入
  2. 2つのViewControllerを作る
  3. スポットライトとテキストを用意
  4. スポットライトとテキストアニメーションを設定

 

Geccoを導入

 

podfile経由でインストール。

f:id:nekokichi_yos2:20200731110915p:plain

 

2つのViewControllerを作る

 

Geccoの実装に必要なのは、

  • Geccoの設定(スポットライトの位置や表示設定など)
  • Geccoの実装(スポットライトを表示させる)

の2つ。

 

今回は、設定用をGeccoVCとして、

(クラスは、SpotlightViewController、と定義してください。)

import UIKit
import Gecco

class GeccoVC: SpotlightViewController {
//各UILabelの座標データ var uiLabels_frames = [CGRect()] //画面サイズ let screenSize = UIScreen.main.bounds.size

}

 

実装用をDisplayGeccoとします。

(先に、スポットライトで指し示すUIを用意しておきます。)

import UIKit

class DisplayGecco: UIViewController {
    
    @IBOutlet weak var button1: UIButton!
    @IBOutlet weak var button2: UIButton!
    @IBOutlet weak var button3: UIButton!
    
}

 

 DisplayGeccoではGeccoを実行するコードを書き、GeccoVCではスポットライトやテキストを表示するのに必要な設定を施していきます。

 

スポットライトとテキストを用意

 

まず、必要なのは、

  • スポットライトのframe
  • スポットライトを進行させるためのdelegate
  • テキストを生成
  • テキストを表示させる処理

の4つ。

 

スポットライトのframe

 

スポットライトでUIを照らすので、そのUIの座標を取得する必要があります。

 

よって、DisplayGeccoのUIButtonを指し示すので、

  1. 各UIButtonの座標を取得
  2. GeccoVCに渡す
  3. スポットライトを生成

を実行します。

 

各UIButtonのCGRectを配列に格納し、

  func passCGRect() -> [CGRect] {
        var arrayCGRect = [CGRect]()
        arrayCGRect.append(button1.frame)
        arrayCGRect.append(button2.frame)
        arrayCGRect.append(button3.frame)
        return arrayCGRect
    }


その配列を、StoryBoardのインスタンスで GeccoVCに渡します。

let geccoVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "Gecco") as! GeccoVC
geccoVC.uiLabels_frames = passCGRect()

 

受け取った座標を元に、スポットライトを生成。

(spotlightView.apperは、最初に表示されるスポットライト)

spotlightView.appear(Spotlight.RoundedRect(center: CGPoint(x: uiLabels_frames[0].midX, y: uiLabels_frames[0].midY), size: CGSize(width: uiLabels_frames[0].width + 40, height: uiLabels_frames[0].height + 40), cornerRadius: 50))

 

次に表示させるスポットライトを生成。

(spotlightMoveは、次に表示されるスポットライト、指定した座標にスポットライトが移動する)

func spotlightMove(_ cgpoint:CGPoint, _ cgsize:CGSize) {
    spotlightView.move(Spotlight.RoundedRect(center: cgpoint, size: cgsize, cornerRadius: 50))
}
spotlightMove(CGPoint(x: uiLabels_frames[1].midX, y: uiLabels_frames[1].midY), CGSize(width: uiLabels_frames[1].width + 40, height: uiLabels_frames[1].height + 40))
spotlightMove(CGPoint(x: uiLabels_frames[2].midX, y: uiLabels_frames[2].midY), CGSize(width: uiLabels_frames[2].width + 40, height: uiLabels_frames[2].height + 40))

 

スポットライトを進行させるためのdelegate

 

次に、スポットライトのアニメーションの実行/解除、を設定します。

 

GeccoVCでSpotlightViewControllerDelegateを記述。

extension GeccoVC: SpotlightViewControllerDelegate {
    //画面が表示される時
    func spotlightViewControllerWillPresent(_ viewController: SpotlightViewController, animated: Bool) {
    }
    //画面タップ時
    func spotlightViewControllerTapped(_ viewController: SpotlightViewController, isInsideSpotlight: Bool) {
    }
    //画面が消える時
    func spotlightViewControllerWillDismiss(_ viewController: SpotlightViewController, animated: Bool) {
        spotlightView.disappear()
    }
}

 

今回は、画面をタップするたびにスポットライトを表示させていくので、下記のメソッドを用意。

(実行されるたびに、updateIndexが加算され、3つのスポットライトが順番に表示され、最後にスポットライトが消える)

(終了後にupdteIndexは初期化されるので、再び実行すればスポットライトが表示される)

    func nextSpotlight() {
        
        switch updateIndex {
        case 0:
            spotlightView.appear(Spotlight.RoundedRect(center: CGPoint(x: uiLabels_frames[0].midX, y: uiLabels_frames[0].midY), size: CGSize(width: uiLabels_frames[0].width + 40, height: uiLabels_frames[0].height + 40), cornerRadius: 50))
        case 1:
            spotlightMove(CGPoint(x: uiLabels_frames[1].midX, y: uiLabels_frames[1].midY), CGSize(width: uiLabels_frames[1].width + 40, height: uiLabels_frames[1].height + 40))
        case 2:
            spotlightMove(CGPoint(x: uiLabels_frames[2].midX, y: uiLabels_frames[2].midY), CGSize(width: uiLabels_frames[2].width + 40, height: uiLabels_frames[2].height + 40))
        case 3:
            dismiss(animated: true, completion: nil)
        default:
            break
        }
         
        updateIndex += 1
    }

 

上記のメソッドをdelegateのメソッド内に記述。

    func spotlightViewControllerWillPresent(_ viewController: SpotlightViewController, animated: Bool) {
        nextSpotlight()
    }
    //画面タップ時
    func spotlightViewControllerTapped(_ viewController: SpotlightViewController, isInsideSpotlight: Bool) {
        nextSpotlight()
    }

 

これで、下記の処理が実装できました。

  1. GeccoVCが開く→1つ目のスポットライト
  2. 画面をタップ→2つ目のスポットライト
  3. 画面をタップ→3つ目のスポットライト
  4. 画面をタップ→スポットライトのアニメーションが終了

 

テキストの生成

 

とりあえず、下記のコードでスポットライトと一緒に表示させるテキストを3つ生成します。

 

また、各テキストの座標はUIButtonの座標の近くに表示させるので、取得したUIButtonの座標を元にframeを指定します。

    var uiLabels = [UIView]()

    override func viewDidLoad() {
        super.viewDidLoad()
        delegate = self
        //使用するラベルは3つなので、0...2
        for index in 0...2 {
            createLabels(index)
        }
    }
    
    //メッセージ用のUILabelを生成
    func createLabels(_ index:Int) {
        let label = UILabel()
        switch index {
        case 0:
            label.text = "ボタン1"
        case 1:
            label.text = "ボタン2"
        case 2:
            label.text = "ボタン3"
        default:
            break
        }
        //labelの設定
        label.textColor = .white
        label.font = UIFont.systemFont(ofSize: 16)
        label.textAlignment = .center
        label.numberOfLines = 0
        label.frame = CGRect(x: 0, y: uiLabels_frames[index].origin.y + uiLabels_frames[index].height + 20, width: screenSize.width, height: 60)
        self.view.addSubview(label)
        //uiLabelsに格納
        uiLabels.append(label)
    }

f:id:nekokichi_yos2:20200731224838j:plain

 

テキストのアニメーションを設定  

 

まずは、下記のメソッドを定義。

(viewは格納したUILabel、indexは要素番号)

(後ほど、解説します。)

    func updateSpotlightLabel(_ labelAnimated: Bool) {
        uiLabels.enumerated().forEach { index, view in
            UIView.animate(withDuration: labelAnimated ? 0.25 : 0) {
                view.alpha = index == self.updateIndex ? 1 : 0
            }
        }
    }

 

スポットライトと同時に表示させるので、上記のメソッドをnextSpotlightの中に記述。

var updateIndex = 0
func nextSpotlight(_ spotAnimated: Bool) {
updateSpotlightLabel(spotAnimated) updateIndex += 1
}

 

updateSpotlightLabelのメソッドは、

  • 生成したUILabelを全て表示
  • updateIndexに対応するテキストのみを表示(それ以外は非表示)

の仕組みで動いてます。

 

つまり、updateIndexの値に対して、

  • 0 : button1、label("ボタン1")
  • 1 : button2、label("ボタン2")
  • 2 : button3、label("ボタン3")

のように対応しており、updateIndexの各値に対応するテキストのみを表示しているのです。

f:id:nekokichi_yos2:20200731224924j:plain



 

スポットライトとテキストを表示

 

そして最後は、GeccoVCをpresentで表示させれば、スポットライトのアニメーションが開始されます。

let geccoVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "Gecco") as! GeccoVC
self.present(geccoVC, animated: true, completion: nil)

 

結果

 

f:id:nekokichi_yos2:20200731103513g:plain
 

ソースコード

 

「ストーリーボード」

f:id:nekokichi_yos2:20200731103311p:plain

 

 

「DisplayGecco」

import UIKit

class DisplayGecco: UIViewController {
    
    @IBOutlet weak var button1: UIButton!
    @IBOutlet weak var button2: UIButton!
    @IBOutlet weak var button3: UIButton!
    
    func passCGRect() -> [CGRect] {
        var arrayCGRect = [CGRect]()
        arrayCGRect.append(button1.frame)
        arrayCGRect.append(button2.frame)
        arrayCGRect.append(button3.frame)
        return arrayCGRect
    }
    
    @IBAction func start(_ sender: Any) {
        let geccoVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "Gecco") as! GeccoVC
        geccoVC.alpha = 0.5
        //GeccoVCのスポットライトやテキストに必要な座標データを渡す
        geccoVC.uiLabels_frames = passCGRect()
        //移行
        self.present(geccoVC, animated: true, completion: nil)
    }
    
}

 

「GeccoVC」

import UIKit
import Gecco

class GeccoVC: SpotlightViewController {
    
    //UILabelを格納
    var uiLabels = [UIView]()
    //各UILabelの座標データ
    var uiLabels_frames = [CGRect()]
    //LabelやSpotlightの表示を決める
    var updateIndex = 0
    //画面サイズ
    let screenSize = UIScreen.main.bounds.size


    override func viewDidLoad() {
        super.viewDidLoad()
        delegate = self
        for index in 0...2 {
            createLabels(index)
        }
    }
    
    //メッセージ用のUILabelを生成
    func createLabels(_ index:Int) {
        let label = UILabel()
        switch index {
        case 0:
            label.text = "ボタン1"
        case 1:
            label.text = "ボタン2"
        case 2:
            label.text = "ボタン3"
        default:
            break
        }
        label.textColor = .white
        label.font = UIFont.systemFont(ofSize: 16)
        label.textAlignment = .center
        label.numberOfLines = 0
        label.frame = CGRect(x: 0, y: uiLabels_frames[index].origin.y + uiLabels_frames[index].height + 20, width: screenSize.width, height: 60)
        self.view.addSubview(label)
        uiLabels.append(label)
    }

    //Spotlightを表示
    func nextSpotlight(_ spotAnimated: Bool) {
        
        updateSpotlightLabel(spotAnimated)
        
        switch updateIndex {
        case 0:
            spotlightView.appear(Spotlight.RoundedRect(center: CGPoint(x: uiLabels_frames[0].midX, y: uiLabels_frames[0].midY), size: CGSize(width: uiLabels_frames[0].width + 40, height: uiLabels_frames[0].height + 40), cornerRadius: 50))
        case 1:
            spotlightMove(CGPoint(x: uiLabels_frames[1].midX, y: uiLabels_frames[1].midY), CGSize(width: uiLabels_frames[1].width + 40, height: uiLabels_frames[1].height + 40))
        case 2:
            spotlightMove(CGPoint(x: uiLabels_frames[2].midX, y: uiLabels_frames[2].midY), CGSize(width: uiLabels_frames[2].width + 40, height: uiLabels_frames[2].height + 40))
        case 3:
            dismiss(animated: true, completion: nil)
        default:
            break
        }
         
        updateIndex += 1
    }
    
    //Spotlightを処理
    func spotlightMove(_ cgpoint:CGPoint, _ cgsize:CGSize) {
        spotlightView.move(Spotlight.RoundedRect(center: cgpoint, size: cgsize, cornerRadius: 50))
    }
    
    //UILabelを表示
    func updateSpotlightLabel(_ labelAnimated: Bool) {
        uiLabels.enumerated().forEach { index, view in
            UIView.animate(withDuration: labelAnimated ? 0.25 : 0) {
                view.alpha = index == self.updateIndex ? 1 : 0
            }
        }
    }

}

extension GeccoVC: SpotlightViewControllerDelegate {
    func spotlightViewControllerWillPresent(_ viewController: SpotlightViewController, animated: Bool) {
        nextSpotlight(false)
    }
     
    func spotlightViewControllerTapped(_ viewController: SpotlightViewController, isInsideSpotlight: Bool) {
        nextSpotlight(true)
    }
     
    func spotlightViewControllerWillDismiss(_ viewController: SpotlightViewController, animated: Bool) {
        spotlightView.disappear()
    }
}

 

参考

 

grandbig.github.io

cpoint-lab.co.jp