ひとしれずひっそり

主にソフトに関することをメモしていきます。過程をそのまま書いていたりするので間違いが含まれます。鵜呑みしない様に。

ファイルのインポートやURL Schemeの処理は onOpenURL を使用する

SwiftUIの前は、ファイルのインポートやURL Schemeで呼び出される際にAppDelegateやSceneDelegateでコールバックが行われていた。

SwiftUIでもUIApplicationDelegateAdaptorでAppDelegateと同じ様なことができる。

struct MyApp: App {
    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

class AppDelegate: NSObject, UIApplicationDelegate {
    func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
        return true
    }

    func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
        debugPrint(url)
        return true
    }
}

しかし、application (_:url:options:)が呼び出されない。


onOpenURL()を使用すると呼び出される。

struct MyApp: App {
    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
    var body: some Scene {
        WindowGroup {
            ContentView()
                .onOpenURL { (url) in
                    debugPrint(url)
        }
    }
}

参照

developer.apple.com

Structureは継承できない

オブジェクトのシリアライズでStructureを定義するとjsonなどに変換しやすくなる。
既存ソフトの置換えをしていて次の様な流れで処理しようとしている。

Export

Object(Class) -> Struct -> JSON

Import

JSON -> Struct -> Object(Class)

Classは継承したものもあって、

struct AContainer {
   var name: String
}

struct BContainer: AContainer {
  var greeting: String
}

という感じにしたら inheritance from non-protocol type 'AContainer' というエラーがでた。

Structures and classes in Swift have many things in common. Both can:
Conform to protocols to provide standard functionality of a certain kind
Classes have additional capabilities that structures don’t have:
Inheritance enables one class to inherit the characteristics of another

プロトコルの適用はできるけど継承はクラスじゃなきゃできないというとだっった。

docs.swift.org

AContainerの中身をBContainerにコピーしなければならない。
AContainerで増減があった場合に忘れずにBContainerも変更しなければならない。

struct AContainer {
   var name: String
}

struct BContainer {
   var name: String
   var greeting: String
}

ファイル生成スクリプト使ってやった方が間違いないかな…と思ってやってたら..

Relationの場合

sturct Root {
  var chidren: [AContainer]
}

ってやりたいのにBContainerはAContainerを継承していないので含めることができない。
AもBも包括するStructureを作ってどちらの型か判別できるpropertyを使わないといけない。

class A {
    init(){
        name = String(describing: type(of: self))
    }
    var name: String
}

class B: A {
    override init(){
        greeting = "Hello"
    }
    var greeting: String
}

struct ABContainer {
    var type: String
    var name: String
    var greeting: String?
}

struct Root {
    var children: [ABContainer] = []
    mutating func addAB(_ obj: A) {
        if let b = obj as? B {
            var container = ABContainer(type: "B", name: b.name, greeting: b.greeting)
            children.append(container)
        } else{
            var container = ABContainer(type: "A", name: obj.name)
            children.append(container)
        }
    }
}


let a = A()
let b = B()

var r = Root()
r.addAB(a)
r.addAB(b)
print(r) // -> Root(children: [__lldb_expr_87.ABContainer(type: "A", name: "A", greeting: nil), __lldb_expr_87.ABContainer(type: "B", name: "B", greeting: Optional("Hello"))])

ところでSubscriptsというのが目に入った。
内部データへのアクセスに関するものらしいけどまた改めて調べることとしよう。

docs.swift.org

参考

zenn.dev

willSave()はsetPrimitiveValue(:forKey)を使え

Core Dataを使っていて保存前に作成時刻や更新時刻を記録したい時にwillSaveのタイミングで記録できる。

developer.apple.com

とりあえずカスタムクラスを作成して、willSaveで普通にプロパティで設定してみる。

import Foundation
import CoreData

@objc(Item)
public class Item: NSManagedObject {

    override public func willSave() {
        super.willSave()
        
        let now = Date()
        if createdAt == nil {
            createdAt = now
        }
        updatedAt = now
    }
}

するとオプジェクト作成時にExceptionが出てクラッシュしてしまった。

プロパティを変更するとchangeフラグがセットされて再帰的にwillSave()が呼び出されクラッシュに至った。

createdAtはnilの時だけしかセットしないので、1回目はchangeフラグが立つが2回目は立たないので、このままでも問題ない。
updatedAtは毎回changeフラグが立ってしまうので問題がある。
試しにupdatedAt = nowの行をコメントアウトするとオブジェクト作成可能である。

@objc(Item)
public class Item: NSManagedObject {

    override public func willSave() {
        super.willSave()
        
        let now = Date()
        if createdAt == nil {
            createdAt = now
        }
        // updatedAt = now
    }
}

updatedAtはsetPrimitiveValue(:forKey:)で対応する。
setPrimitiveValue()はchangeフラグを立てない。

以前hasChangesにならないので悩んだがこれではっきりした。

katsuyoshi.hatenadiary.com

@objc(Item)
public class Item: NSManagedObject {

    override public func willSave() {
        super.willSave()
        
        let now = Date()
        if createdAt == nil {
            createdAt = now
        }
        setPrimitiveValue(now, forKey: "updatedAt")

    }
}

今度はupdatedAtでも問題ない。

createdAtも2回呼び出されるのでsetPrimitiveValue()を使う方がいいだろう。

developer.apple.com

コードはこちら
CoreDataのwillSave()を使う際はsetPrimitiveValue(:forkey:)を使う · GitHub

余白を押した時に反応させる場合はcontentShapeを使う

ListやFromを使用した時のCellにTextを使ったとして、表示される文字を押すと反応するが、余白を押すと反応しない。
contentShape()を使用すると余白にも反応する。
毎回忘れて検索するので、書いておく。

struct ContentView: View {
    @State var showingAlert = false
    var body: some View {
        HStack {
            Text("Click me")
            Spacer()
        }
        //.background(.green)
        .padding()
        .contentShape(Rectangle())
        .onTapGesture {
            showingAlert.toggle()
        }
        .alert(isPresented: $showingAlert) {
            Alert(title: Text("Hello there!"))
        }
    }
}

ただ、From内だとNavigationLink使ってないせいかframe指定もしないとだめだった。
しかし、background指定したりいろいろやってからframe指定外したらちゃんと反応する様になって再現できなくなった。
ここら辺はXcodeとかの不具合かもしれない。

struct ContentView: View {
    @State var showingAlert = false
    var body: some View {
        Form {
            HStack {
                Text("Click me")
                Spacer()
            }
            //.background(.green)
            //.frame(maxWidth: .infinity)
            .contentShape(Rectangle())
            .onTapGesture {
                showingAlert.toggle()
            }
        }
        .alert(isPresented: $showingAlert) {
            Alert(title: Text("Hello there!"))
        }
        .padding()
    }
}

NavigationLink使うとcontentShapeはいらいな。

struct ContentView: View {
    var body: some View {
        NavigationStack {
            Form {
                NavigationLink {
                    AlertView()
                } label: {
                    HStack {
                        Text("Click me")
                        Spacer()
                    }
                }
            }
            .padding()
        }
    }
}

参照

Apple Developer Documentation

swiftui - NavigationLink contentShape not working without adding a background color - Stack Overflow

ios - Swiftui How can you use on Tap Gesture for entire row in a foreach loop - Stack Overflow

M5Stamp S3 (3) Lチカ

M5Stamp S3 でLチカをしてみる。

M5Unifiedで使ったのにちょっと手を加えただけで動いた。

katsuyoshi.hatenadiary.com

LEDはM5Stamp picoのソフトほぼそのままで動いた。
そのままで動いたということはRGBではなくGRBの順になっている様だ。

gist.github.com


Arduino IDEで試した時に入れたM5AtomS3ライブラリーも試してみる。

コンパイルしてみるとUSBSerialがないといわれる。

.pio/libdeps/esp32-s3-devkitc-1/M5AtomS3/src/M5AtomS3.cpp:20:9: error: 'USBSerial' was not declared in this scope

シリアル動作させるためにbuild_flagsに入れていたが、それを外すとコンパイルできた。
M5Stamp picoはM5Atomをライブリーで使用できたのでM5Stamp S3もM5AtomS3ライブラリーで使用できそうだがplatformio環境ではまだ時期尚早なのかもしれない。

[env:esp32-s3-devkitc-1]
platform = espressif32
board = esp32-s3-devkitc-1
framework = arduino
;build_flags = 
;   -DARDUINO_USB_MODE=1
;   -DARDUINO_USB_CDC_ON_BOOT=1
monitor_speed = 115200
lib_deps = 
    fastled/FastLED@^3.5.0
    m5stack/M5AtomS3@^0.0.2

M5Unifiedが対応されたら使うのがいいかもしれない。
使わなくてもesp32-s3-devkitc-1のまま使えそうな気がする。

3軸トゥールビヨンの置き時計【3Dプリント用STLデータ】購入した(6)

暫く放置していたけど少しずつ進めないとと思って少し進めた。

組み立てはこちらを見て進める。
www.youtube.com

電源ソケットのケーブルが短かった。
思わぬハンダ付け作業が入ってしまいゼンマイユニットの固定までで終えた。

次回は動画の 2:15 くらいからスタート。