ずっと5月

3日坊主してます

SwiftUIのForEachでViewの繰り返し表示が上手く出来なかったときのメモ

上手く行かないやつ
init時点でstrListに存在するものに関しては表示されるが、途中で追加されたものが描画されなかった

class StateValue: ObservableObject {
    @Published var strList: [Moji]

    struct Moji {
        var title: String
        var location: CGPoint
        var size: CGFloat
        var angle: Angle
        var color: Color
        
        public init( title: String,
                     location: CGPoint = .init(x: 120, y: 20),
                     size: CGFloat = 20,
                     angle: Angle = .init(degrees: 0.0),
                     color: Color = .black
        ) {
            self.title = title
            self.location = location
            self.size = size
            self.angle = angle
            self.color = color
        }
    }
ForEach(0..<list.count) { index in
                        let item = list[index]
                        Text(item.title)
                            .foregroundColor(item.color)
                            .position(item.location)
                            .rotationEffect(item.angle)
                            .gesture(
                                DragGesture()
                                        .onChanged { value in
                                            state.strList[index].location = value.location
                                            state.isDragging = true
                                        }
                                        .onEnded { _ in
                                            state.isDragging = false
                                        }
                            )
                    }

上手く行かないのはForeachの仕様らしい
最終的に繰り返したい構造体をIdentifiableに準拠させて、Foreachの書き方を変えて動作させた

class StateValue: ObservableObject {
    @Published var strList: [Moji]

    struct Moji: Identifiable {  // Identifiableに準拠させる
        var id = UUID()
        var title: String
        var location: CGPoint
        var size: CGFloat
        var angle: Angle
        var color: Color
        
        public init( title: String,
                     location: CGPoint = .init(x: 120, y: 20),
                     size: CGFloat = 20,
                     angle: Angle = .init(degrees: 0.0),
                     color: Color = .black
        ) {
            self.title = title
            self.location = location
            self.size = size
            self.angle = angle
            self.color = color
        }
    }
ForEach(Array(list.enumerated()), id: \.offset) { index, item in 
                        Text(item.title)
                            .foregroundColor(item.color)
                            .position(item.location)
                            .rotationEffect(item.angle)
                            .gesture(
                                DragGesture()
                                        .onChanged { value in
                                            state.strList[index].location = value.location
                                            state.isDragging = true
                                        }
                                        .onEnded { _ in
                                            state.isDragging = false
                                        }
                            )
                    }

developer.apple.com

ja.stackoverflow.com

iOSアプリのアーカイブ方法の種類

配布方法の選択

App Store Connect: リリース版

Ad Hoc: リモートでのテスト用 →委託開発でアプリ開発している場合 アプリ(ipaファイル)をメールなどで配布し、iPhoneiPadにインストールする方式
事前にProvisioning Portal(プロビジョニングポータル)で、デバイスの登録を行っておく必要がある

Enterprise: 企業内用 セキュアなサーバーにアプリ(ipaファイル)、マニフェストファイル(plist)をアップして、URLからインストール
バイスの事前登録が不必要で、何台でもインストール出来る   OTAとかで使えるようにしたのがこれ

Development: 開発用

f:id:uminokaze521:20220307111035p:plain

KeyChain調査メモ

KeyChainとは?

UserDefaultと比較してセキュアに値を保存できる アプリを削除してもデータは残る(AppleIDに紐づく) 大量のデータ保存には向かない

使い方

(そのまま使うより神が作ったライブラリを使ったほうが楽らしい)

検索

任意の値に一致したデータが存在するか確認したいときは↓の関数 func SecItemCopyMatching(_ query: CFDictionary, _ result: UnsafeMutablePointer<CFTypeRef?>?) -> OSStatus 引数にクエリ(検索条件)と結果が見つかったときの結果の参照(ポインタ)を持つ

let search: [String: Any] = [kSecClass as String: kSecClassGenericPassword,
                             kSecAttrService as String: "service",// サービス名 必須じゃない。kSecAttrAccountと合わせてユニーク
                             kSecReturnAttributes as String: kCFBooleanTrue as Any, // kSecReturnAttributes:属性を返すか返さないか
                             kSecAttrAccount as String: "account1", // アカウント名 kSecAttrServiceと合わせてユニーク
                             kSecMatchLimit as String: kSecMatchLimitOne] as [String : Any] // 上限

// 保存データが存在するかの確認
let res = SecItemCopyMatching(search as CFDictionary, nil)

SecItemCopyMatchingの結果のステータスでこの辺は使う機会多いかも errSecItemNotFound:データが無かったとき errSecSuccess:エラーが無く成功(データが見つかったとき) errSecDuplicateItem:アイテムが重複していたとき 他↓参照 https://developer.apple.com/documentation/security/1542001-security_framework_result_codes

データの保存

// パスワード
let str = "password"
let data = str.data(using: .utf8)
guard let _data = data else {
        return false
}
let dic: [String: Any] = [kSecClass as String: kSecClassGenericPassword,
                          kSecValueData as String: _data,                // 保存したいもの
                          kSecAttrService as String: "service"]            // サービス名

var itemAddStatus: OSStatus? = itemAddStatus = SecItemAdd(dic as CFDictionary, nil)

削除

let dic: [String: Any] = [kSecClass as String: kSecClassGenericPassword,
                                  kSecAttrService as String: "service"]

if SecItemDelete(dic as CFDictionary) == errSecSuccess {
      // 削除成功したとき
} else {
      // 削除失敗したとき
}

その他メモ

保存するものの種類(パスワード、証明書、暗号鍵など)によって付属できる属性が変わる https://developer.apple.com/documentation/security/keychain_services#//apple_ref/doc/uid/TP30000898-CH4g-SW18

kSecAttrAccountとkSecAttrServiceの値を使ってユニークなデータとして保存している。kSecAttrServiceが違えばkSecAttrAccountが同じでもOK kSecAttrService は必須ではなくてkSecAttrAccount だけで運用可。 *その場合は同じ kSecAttrAccount の値は複数登録できない

参考

https://qiita.com/ayutaso/items/960e1b1f553e53ca60f4 https://ameblo.jp/xcc/entry-10813323465.html http://cocoadays.blogspot.com/2011/02/ios-keychain-services.html https://qiita.com/mario7/items/11f06502a022c01cfbb5

UIViewの拡大・縮小

拡大・縮小

UIView.transition(with: self.squareView,
                          duration: 1.0,
                          options: [.transitionFlipFromLeft, .repeat, .autoreverse],
                          animations: {
                            self.squareView.transform = CGAffineTransform(scaleX: 2, y: 2); // これを(scaleX: 1/2, y: 1/2)にすると1/2の大きさになる
        },
                          completion:  nil)

f:id:uminokaze521:20210301223957g:plain

UIViewのアニメーションについてのメモ

UIView.animateによるアニメーション

duration は何秒でアニメーションさせるか
delay で開始まで何秒遅延するか指定
options でアニメーションの動き方を指定 (後述)
animations で動かしたいUIViewのプロパティを変化
completion は完了した後に呼ばれるクロージャ

// optionsを複数指定したい場合は[.repeat,.autoreverse, xx, xx...]な感じで指定する
UIView.animate(withDuration: 1.0,
                       delay: 0.0,
                       options: .autoreverse,
                       animations: {
                        self.squareView.center.y += 100.0
                       },
                       completion: nil)

f:id:uminokaze521:20210301213828g:plain

UIView.transitionによるアニメーション

UIView.transition(with: self.squareView,
                          duration: 1.0,
                          options: [.transitionFlipFromLeft, .repeat, .autoreverse],
                          animations: {
                            self.squareView.backgroundColor = UIColor.yellow
        },
                          completion:  { (finished: Bool) in
                            self.squareView.backgroundColor = UIColor.blue
        })

f:id:uminokaze521:20210301221715g:plain

optionsについて

指定できるoptionはたくさんあるのでめぼしいものをメモしておく

option名 説明
autoreverse 逆再生する
repeat 繰り返し
curveEaseIn アニメーションが進行するにつれて早くなる
curveEaseIn アニメーションが進行するにつれて早くなり、完了する前に再び遅くなる
curveEaseOut アニメーションが進行するにつれて遅くなる
curveLinear 等速
transitionFlipFromLeft 垂直軸を中心に左から右に反転する
transitionFlipFromRight 垂直軸を中心に右から左に反転する
transitionFlipFromTop 水平軸を中心に上から下に反転する
transitionFlipFromBottom 水平軸を中心に下から上に反転するトランジション

複数の処理を連続的に行う

open class func addKeyframe(withRelativeStartTime frameStartTime: Double, relativeDuration frameDuration: Double, animations: @escaping () -> Swift.Void)

を使う

UIView.animateKeyframes(withDuration: 2.0, delay: 0.0, options: [.repeat], animations: {

            UIView.addKeyframe(withRelativeStartTime: 0.0, relativeDuration: 1, animations: {
                self.squareView.center.y += 100.0
                self.squareView.center.x += 100.0
                self.squareView.transform = CGAffineTransform(rotationAngle: 360)
            })

            UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: 1, animations: {
                self.squareView.center.y -= 100.0
                self.squareView.center.x -= 100.0
                self.squareView.transform = CGAffineTransform(rotationAngle: 360)
            })

        }, completion: nil)

参考

qiita.com

Swiftで袋文字を作る

袋文字を作りたくて、UISliderで文字の大きさとフチのサイズを可変にする方法を調べた。

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var label: UILabel!
    @IBOutlet weak var fontSize: UISlider!
    @IBOutlet weak var edgeSize: UISlider!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
    }

    @IBAction func fontSizeChanged(_ sender: Any) {
        label.font = UIFont.boldSystemFont(ofSize: CGFloat(fontSize.value))
    }
    
    @IBAction func edgeSizeChanged(_ sender: Any) {
        if let text = label.text {
            label.attributedText = NSAttributedString(string: text, attributes: label.makeAttributedString(font: label.font, strokeEdge: edgeSize.value, foreGroundColor: UIColor.black, strokeColor: UIColor.green))
        }
    }
}

extension UILabel {
    
    func makeAttributedString(font: UIFont, strokeEdge: Float, foreGroundColor: UIColor, strokeColor: UIColor) -> [NSAttributedString.Key : Any] {
        
        return [.strokeWidth : strokeEdge,
                .font : font,
                .foregroundColor :foreGroundColor,
                .strokeColor : strokeColor
            ] as [NSAttributedString.Key : Any]
    }
}

f:id:uminokaze521:20200823221947g:plain
実際に動かした動画

この後はカラースライダーで文字の色を可変にする方法を探したい

参考にしたページ

www.macneko.com

ローマ数字からアラビア数字に変換する

LeetCodeの Roman to Integerっていう問題を解いた
今回はローマ数字からアラビア数字だったけど逆の問題もあるのでそのうち解きたい

class Solution {
    func romanToInt(_ s: String) -> Int {
        var sum:Int = 0
        var lastVal = 0
        
        let valueMap: [Character: Int] = ["I": 1,
                                       "V": 5,
                                       "X": 10,
                                       "L": 50,
                                       "C": 100,
                                       "D": 500,
                                       "M": 1000]
        
        for char in s.reversed(){
            if let val = valueMap[char] {
                if val >= lastVal {
                    sum += val
                } else {
                    sum -= val
                }
                lastVal = val
            }
        }
        return sum
    }
}