投稿者: itkuma

  • VisionProデモ体験

       VisionProデモ体験

     近視の老眼の場合どうなるんだろうと気に

    なってました。それを知りたくて、Apple

    StoreでVisionProのデモ体験をしてきました。

     最初に近視のメガネを専用の機械で解析し

    てくれました。そして、それに合うZEISS

    Optical Insertsなるレンズを入れてくれてまし

    た。

     仮想ディスプレイは近くにしても遠くにし

    てもはっきり見えました。どうやら老眼の事

    は気にしなくても良いようです。

     音響も素晴らしく、最初に誕生日のお祝い

    の映像で蝋燭を吹き消す場面があるのですが、

    蝋燭を吹き消す息が、顔を通り、耳元を通り

    すぎていく感じがリアルでした。

     また、バスケットボールの3D映像では、

    ボールがこちらに飛んでくる場面で、反射的

    に体が動いてしまいました。

     三十分程度のデモでしたが、とてもワクワ

    クする経験でした。

  • 初めてのアプリ登録

    Apple Developer Program に登録して、1年以上紆余曲折しながら、初めてApp Store Connectにアプリの審査申請をしてみました。Apple Developer Program に登録したての頃は軽い気持ちで取り組んでました。ところが、いろいろ情報を得ていくうちに今までの知識ではとても間に合わないことに気づき、休日を利用し基本的なところから、まず学習していきました。そのかいあってか簡単なアプリを作ることができたので、ここは試しにアプリの登録をしようとこれまた軽い気持ちで、審査申請に挑戦しました。まずアプリをどうやって審査申請のまな板にのせるかがわからず、ネットを調べたら、XCodeのメニューproduct の archiveでできるとのこと、さっそくやってみました。そして、登録申請のボタンがあるのでとりあえず、クリックすると、メッセージの山です。登録上必要事項が記入されていないと、メッセージが出て、審査申請が完了しません。何度もメッセージに従って必要事項を記入して、根負けしそうになりながらも、何度も審査申請をクリックすると、やっと、審査中になりました。そして、2日目に配信準備完了になりました。同時に何通か来ていたメールに、配信への手順の記載があり、それに従い必要項目を設定し配信できました。AppStoreでの表示はその翌日に可能となりました。

    申請にはプライバシポリシーの保存先URLと、サポートページのURLを記載する必要がありました。このサイトの一部を使用することで対応できました。このサイトはそんなことは全く想定しておらず勢いで作成していたのですが、この時ばかりはこのサイトを運営していてよかった思いました。

  • SwiftData @Modelを使ってList項目の追加・移動・削除を実装してみました

    SwiftDataの@Modelを使って、Listの項目を移動させる処理を作ってみました。@Modelはクラスに適用することで、アプリを終了させてもプロパティの値を保持させることができます。ただしアプリを削除したらプロパティの値は消滅します。

    まず、@Modelを適用させるクラスです。

    import Foundation
    import SwiftData
    
    @Model
    final class Item: Identifiable {
        var name: String
        //チェックボックス表示切り替え用
        var check: Bool
        //移動処理時にソートをかける対象
        var sortNumber: Int
        //移動処理をかけるときにIdentifiableプロトコルを準拠させるのに必要
        var id = UUID()
            
        init(name: String, sortNumber: Int) {
            self.name = name
            self.check = false
            self.sortNumber = sortNumber
        }
        init(item: Item) {
            self.name = item.name
            self.check = item.check
            self.sortNumber = item.sortNumber
        }
    }

    Itemの配列をリストとして表示し項目の追加、移動、削除の処理を可能にしたViewがこちらです。

    import SwiftUI
    import SwiftData
    
    struct ItemListView: View {
        //@Model class Item を modelContextとしてしようできるようにします
        //これでmodelContextの insert delete が利用できます
        @Environment(\.modelContext) var modelContext
        //Listのエディットモードを切り替える変数 editModeを設定
        @Environment(\.editMode) var editMode
        //ItemのsortNumberプロパティを並び替えの対象としてItemの配列をnumbersとして設定
        @Query(sort: \Item.sortNumber) private var numbers: [Item]
        @State var shoppingItem: String = ""
        @State var modeName: String = "追加削除モード開始"
    
        var body: some View {
            VStack {
                if editMode?.wrappedValue.isEditing == true {
                    HStack{
                        //買い物リストのアプリにしているので↓
                        TextField("ここに買い物するものを入力してください", text: $shoppingItem)
                            .font(.system(size: 30))
                            .background(Color.yellow)
                        Button{
                            let i = numbers.count
                            modelContext.insert(Item(name: shoppingItem, sortNumber: i))
                            shoppingItem = ""
                        } label: {
                            Text("追加")
                                .font(.system(size: 24))
                        }
                    }
                    .padding()
                }
                //[Item]のnumbersをListで表示
                List {
                    ForEach(numbers) { number in
                        HStack {
                            //チェックボックスの表示を切り替えると表示文字の大きさと色を変更
                            if number.check == false {
                                Text(number.name)
                                    .font(.system(size: 24, weight: .bold, design: .rounded))
                                    .foregroundColor(.black)
                            } else {
                                Text(number.name)
                                    .font(.system(size: 18))
                                    .foregroundColor(.gray)
                            }
                            Spacer()
                            //チェックボックスの表示を切り替え
                            if number.check == false {
                                Image(systemName: "square")
                                    .scaledToFill()
                                    .onTapGesture {
                                        number.check = true
                                    }
                            } else {
                                Image(systemName: "checkmark.square")
                                    .scaledToFill()
                                    .onTapGesture {
                                        number.check = false
                                    }
                            }
                        }
                    }
                    //移動時の処理
                    .onMove{fromOffSet,newOffset in
                        var fromIndex: Int = 0
                        var toIndex: Int = 0
                        var tempItem: [Item] = numbers
    
                        //fromOffSetの配列オブジェクトから並び替え用の数値を取得
                        //これが移動対象の並び替え用の数値
                        for item in fromOffSet {
                            fromIndex = item
                        }
                        
                        if newOffset > fromIndex{
                            toIndex = newOffset - 1
                        }else{
                            toIndex = newOffset
                        }
                        //項目の移動
                        tempItem.swapAt(fromIndex, toIndex)
                        //sortNumberを整える
                        for i in 0..<tempItem.count {
                            tempItem[i].sortNumber = i
                        }
    
                        //いったん全削除
                        for i in 0..<numbers.count {
                            modelContext.delete(numbers[i])
                        }
                        //整った配列を追加
                        for i in 0..<tempItem.count {
                            modelContext.insert(Item(item: tempItem[i]))
                        }
                    }
                    //削除時の処理
                    .onDelete{ indexSet in
                        //
                        for item in indexSet {
                            let obj = numbers[item]
                            modelContext.delete(obj)
                            //削除処理後並び替え用の数値を変化させます
                            //これにより移動処理後に項目の並び替えが行えます
                            //項目追加時に配列の個数を元に並び替え用数値を設定しているのでここで全体の数値を整理しています
                            if obj.sortNumber < numbers.count - 1 {
                                for i in obj.sortNumber..<numbers.count {
                                    numbers[i].sortNumber -= 1
                                }
                            }
                        }
                    }
                    .listRowBackground(Color.yellow)
                }
                .scrollContentBackground(.hidden)
                .background(.clear)
    
                Button {
    
                    if editMode?.wrappedValue.isEditing == true {
                        editMode?.wrappedValue = .inactive
                        modeName = "追加削除モード開始"
                    } else {
                        editMode?.wrappedValue = .active
                        modeName = "追加削除モード終わり"
                    }
                    //ソート用ナンバーを再設定
                    if numbers.isEmpty == false  {
                        for i in 0...numbers.count - 1 {
                            numbers[i].numberSet(sortNumber: i)
                        }
                    }
    
                }label: {
                    Text(modeName)
                        .padding()
                        .font(.system(size: 24))
                        .foregroundColor(.black)
                        .background(Color.green)
                        .cornerRadius(10)
                }
            }
            .padding()
        }
    }
    
  • TestFlightアプリ登録App Reviewに関する情報で電話番号がはじかれる

    TestFlightアプリ登録時 App Reviewに関する情報の電話番号の入力で0から始まる電話番号を入れたら、無効扱いされました。国際電話番号表記にしないといけなかったらしく、はじめの0を日本の国際電話番号+81 に変えたらすんなり受け入れたもらえました。改めて、グローバル感につつかれた感じです。

  • XCodeアプリarchiveでiconのエラー

    簡単なアプリができたので、試しにTestFlightに登録してみようと、XCodeでarchiveをかけて登録しようとしたら、iconの透過ができないかアルファを含んでるとかなんとかで登録できませんでした。結局ファイル書き出し時にPNG形式にして、アルファのチェックを外した物をアイコンにしたらすんなり通りました。assetsに登録時に教えてほしいけど、Apple Developer Programに登録してなくてもアプリを作れるから、その辺はルーズにしてあるのかも・・・。以後気を付けます。

  • 逃げる

      逃げる

     逃げるということになぜか、恥ずかしいと

    か、卑怯というような、イメージを植え付け

    られている気がします。

     逃げるという言葉の意味を調べてみると、

    危険や煩わしいことから積極的に遠ざかると

    か、追手の力の及ばないところに身を置くと

    か、競技においては首位を行くものが後続す

    るものに追いつかれないで勝つといった意味

    でした。恥ずかしさを感じたり、卑怯なこと

    をしているイメージはなさそうです。どこで

    そんなイメージを植え付けられたんでしょう。

    私が小さい頃のテレビの中の人が時々

    「卑怯者、逃げるな!」

    みたいなことを言っていたのを思い出します。

    どうやら、逃がした側の感情のようです。そ

    こから恥ずかしいという感情も派生したので

    しょうか。どちらにしても逃げる側がその行

    為に負の感情を抱く必要はみじんもなさそう

    です。逃げた先で安心な暮らしができれば、

    逃げきっての勝ちといったところでしょうか。

  • SwiftUIの@Observableと@Environmentについて調べてみました

    SwiftUIの@Observableをクラスに適用すると、そのクラスを@EnvironmentでViewに導入すると、そのクラスのプロパティに変更を加えた時その変更を随時Viewに適用してくれます。

    下記コードはTestObservableクラスに@Observableを適用し、ContentViewに@Environmentでそのクラスを導入したものです。

    旧姓、新姓のボタンをクリックすると、クラスのnameプロパティを変化させます。その変化に対応しクラスのnameプロパティをセットしたTextの表示が変化します。

    PreviewもViewにenvironmentでクラスを導入することで、Previewも変化します。

    import SwiftUI
    
    struct ContentView: View {
        @Environment(TestObservable.self) var testObservable
        
        var body: some View {
            Text(testObservable.name)
            Button {
                testObservable.changeName(name: "旧姓")
            } label: {
                Text("旧姓")
            }
            Button {
                testObservable.refreshName()
            } label: {
                Text("新姓")
            }
        }
    }
    
    @Observable
    class TestObservable {
        var name: String
        
        init() {
            self.name = "新姓"
        }
        
        func refreshName() {
            self.name = "新姓"
        }
        
        func changeName(name: String) {
            self.name = name
        }
    }
    
    #Preview {
        ContentView()
            .environment(TestObservable())
    }
    

    @mainからの呼び出しは下記の感じです。

    import SwiftUI
    
    @main
    struct SwiftUILearnApp: App {
        var body: some Scene {
            WindowGroup {
                ContentView()
                    .environment(TestObservable())
            }
        }
    }
    

  • 暑さ寒さも彼岸まで

    暑さ寒さも彼岸まで

     2024年の夏は暑かった。今までにない

    というか、年齢のせいかもしれませんが、暑

    くて、陽の出ている時間にサックスを吹きに

    行く気がしませんでした。

     9月になっても暑い日が続き、彼岸過ぎて

    も暑いのではと思っていると、彼岸前の雨で

    急に気温が下がり、お彼岸には日陰で風にあ

    たると心地よく感じられるようになりました。

    「暑さ寒さも彼岸まで。」

    よく使われる言葉ですが、この時はほんとに

    実感しました。というよりなんだかありがた

    みさえ感じました。涼しくなったのは、言葉

    のおかげではないのですが、昔からある言葉

    の情景が今もまだあることへの感謝なのかも

    しれません。

  • CloudKit iCloudにサインインできない

    CloudKitを学習しようとサンプルプログラムをダウンロードしたり、参考になるサイトをまねてみたりして、実行させるとなぜか

    「iCloudにサインインしてからにして」

    みたいなメッセージが出てしまう。iPhoneでも、macでもiCloudにサインインして、driveを有効にしているのに、なぜなのか。途方に暮れてしまいました。こういう時はいつも、Apple Developer Ducumentationサイトを確認することにしています。CloudKitを一つ一つ見ていくと、Share Recordsにサンプルプログラムがありました。今まで見つけられなかったサンプルプログラムです。こういう時は、宝物でも見つけた気分になります。

    早速ダウンロードして、実行して見ます。

    「iCloudにサインインしてからにして」

    みたいなメッセージがまた出てしまう。

    今度は、ひとつひとつコードを見ていきます。

    「iCloudのcontainer identifierが”iCloud.<bundle identifier>”でなければcontainer作る時は

    CKContainer(identifier: <your container ID>)

    を使え!!」(もとは英語です。結構意訳してます。)

    いままで見なかったコメントでした。早速変更して・・・。

    今度はエラー

    「CloudKitShare.entitlementsがどうたらこうたら」

    ちょっとイラっときて、ファイルからCloudKitShare.entitlementsを消してあげました。再度Signing & CapabilitiesでiCloudのContainersを設定して・・・。CloudKitShare.entitlementsはできません。

    「エラーが消えてる。!」

    ダメもとで、早速実行。

    「うごいた。」

    結局、コメントをよく読むと、Bundle Identifierの頭に「iCloud.」をつけたContainerであれば、

    let container = CKContainer.defualt()

    Bundle Identifierの頭に「iCloud.」をつけたContainerでなければ、

    let container = CKContainer(identifier: <your container ID>)

    にする。ただし、<your container ID>の頭は iCloud. で始まらなければいけないということでした。

  • CloudKit Console レコードタイプが削除できない

    CloudKitを試そうと色々触っているうちにレコードタイプが消せなくなりました。出てくるダイアログを見ると、

    “invalid attempt to delete a record type which is active in a production”

    とメッセージが出て消せません。どうやら、

    「本番コンテナでアクティブなレコード タイプを削除しようとする試みは無効です。」

    といった意味なようです。本番コンテナー?いつした?どこでした?疑問の嵐が巻き起こりです。それから、https://developer.apple.com/documentation/cloudkitを調べてみました。次のようなことがわかりました。私の悪い癖で、説明書とかを読みながらさわればよさそうなものを、適当に触ってつまずいてから、なんでだろうと調べ出すのは悪い癖なんですが、治りません。でも、安心してください。壊れて困るようなものを触る時は必ず説明書等を読みます。

    さて、どうしたら本番環境になるのかですが、Apple Developer Programに登録されていることを前提にして進めます。

    まず、CloudKit DatabaseへはAccount – Apple Developerからも入れます。

    Xcodeからも入れますが、その説明は割愛します。

    Account – Apple Developerのサイトの下記

    赤四角マークのCloudKitをクリック

    赤四角マークのCloudKit Databaseをクリックして、データベースの管理画面に入れます。

    CloudKit Databaseを開くと、左下に下の表示があります。(赤四角や青四角はありません。)

    赤四角のDeploy Schema Changes を実行すると下のような表示が出ます。Schemaの変更をするとここに表示され、右下のDeployボタンをクリックすると、変更したSchemaがDevelopment(開発環境)からProduction(本番環境)へ保存されます。

    そして、

    ただし、レコードタイプは開発環境から本番環境に保存されますが、レコードは開発環境から本番環境に保存されません。

    また、青四角のReset Environmentを実行すると、すべての開発環境のレコードタイプおよびレコードは削除されます。本番環境のレコードタイプおよびレコードは削除されません。本番環境で編集する必要があるようです。本番環境にアプリを移行していなければ、この操作は必要なさそうです。

    開発環境と本番環境の切り替えですが、コンテナ名の右に下の表示があります。そこで開発環境と本番環境を替えらえます。

    上の表示をクリックすると、下の表示が出ますので、そこで環境を選べます。