[Swift] 23 Apple WatchのComplication改良 カレンダーの視認性向上

[M1 Mac, Ventura 13.3.1, Xcode 14.3]

Complicationのカレンダーがとても見にくいカラー構成になっているため、フォントサイズと合わせて改良しました。以下の手順になります。

1.watchOS Appのプロジェクトを作成する。
2.Hello World表示のままコードに手を加えずにWidget Extensionを追加する。
3.swiftコードのEntryViewを以下のように書き換える。今回コードはChatGPTに考えさせた。

struct DateToolComplicationEntryView : View {
    var entry: Provider.Entry

    var body: some View {
        VStack {
            Text(getWeekday(entry.date))
            .font(.largeTitle)
            .foregroundColor(.yellow)
            
            Text(getDay(entry.date))
            .font(.largeTitle)
            .foregroundColor(.white)
        }
    }
    
    func getWeekday(_ date: Date) -> String {
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "E"
        dateFormatter.locale = Locale(identifier: "ja_JP")
        return dateFormatter.string(from: date)
    }

    func getDay(_ date: Date) -> String {
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "d"
        return dateFormatter.string(from: date)
    }
}

4.ビルド後、Apple Watch実機にてComplicationに改良カレンダーを登録し文字盤に表示させる。

既存カレンダー
改良カレンダー

Apple Watchは21年1月から使用していますが、当初からカレンダーの視認性が低いと思っていました。2年と6ヶ月が経ち、ようやくChatGPTの力を借りて解決できました。

ただ画面ロック時にメモアプリと共に表示がブランクになってしまいます。ChatGPTのGPT-4でも解決策は分からず、Stack Overflow英語版やApple開発者フォーラムにも情報はありませんでした。

ロック時の文字盤

プログラミングはAIを使いこなすことでかなりハードルが下がりますね。記憶力よりも応用力の時代になっていくのでしょうか。

[Swift] 22 メモアプリ製作 その8 Complications対応

[M1 Mac, Ventura 13.3.1, Xcode 14.3]

メモアプリを以下の方法でComplicationsに対応させ、Apple Watch文字盤にアイコンを表示させてタップで呼び出せるようにしました。

1.Xcodeの[File]-[New]-[Target]でwatchOSタブにあるWidget Extensionを選択し進めていくと、新ターゲットとしてプロジェクト内にディレクトリが作成される。”Include Configuration Intent”のチェックは外しておく。

2.swiftコードのEntryViewの内容を適当に書き換える。デフォルトではデジタル時計になっている。今回はChatGPTに考えてもらったコードをペーストした。アイコンとしてシステム画像”square.and.pencil”を表示する。

struct MemoToolAW_ComplicationsEntryView : View {
    var entry: Provider.Entry // 他のコードで使うので消さずに残しておく

    var body: some View {
        Image(systemName: "square.and.pencil")
        .resizable()
        .aspectRatio(contentMode: .fit)
        .foregroundColor(.white)
        .background(Color.blue)

//        Text(entry.date, style: .time) // デフォルトはデジタル時計
    }
}

3.ビルド後、Apple Watch実機にてComplicationにメモアプリを登録し文字盤に表示させる。

メモアプリは左の青いアイコン

これまで使っていたメモアプリNotebookはComplicationから起動させると最初に音声入力画面になり内容を表示させるにはこれをキャンセルする必要がありました。今回作ったメモアプリはいきなり内容表示になるので手間が減りました。

参考サイト

[Swift] 21 メモアプリ製作 その7アイコン設定

[M1 Mac, Ventura 13.3.1, Xcode 14.3]

メモアプリのアイコンを作成し、設定しました。

Adobe XDで2048×2048のアイコン画像を作成し、自製アプリでiOS用のアイコンセットを作成しました。ネットサービスでも無料で作ってもらえます。

iOSアプリプロジェクトのAssetsにあるAppIconにアイコンセットの中身をまるごとドラッグ&ドロップします。watchOSアプリの方は1024pxだけでOKです。

[Swift] 20 メモアプリ製作 その6 TextEditorの行数を無制限にする

[M1 Mac, Ventura 13.3.1, Xcode 14.3]

macOS版にメモを10行より多く書くとアプリが落ちてしまいました。

行数を無制限にするには以下のようにframeというビュー修飾子で設定します。

import SwiftUI

struct Draft: View {
    @State var text = ""
    @FocusState var nameFieldIsForcused: Bool
    @Environment(\.managedObjectContext)var viewContext
    var note: Note?

    var body: some View {
        TextEditor(text:$text)
            .frame(minHeight: 0, maxHeight: .infinity) // 行数無制限
            .focused($nameFieldIsForcused)
            .onAppear{
                DispatchQueue.main.asyncAfter(deadline:DispatchTime.now()+0.5){
                    nameFieldIsForcused = true
                }
            }
            .toolbar{
                ToolbarItem(placement:.navigationBarTrailing){
                    if(nameFieldIsForcused){
                        Button(action:{
                            if note != nil{
                                updateContent(note: self.note!)
                            }else{
                                addContent()
                            }

                        }){
                            Text("完了")
                        }
                    }
                }
            }
    }

    func updateContent(note:Note){
        note.content=text
        
        do{
            try viewContext.save()
        }catch{
            fatalError("セーブに失敗")
        }

        nameFieldIsForcused = false
    }

    func addContent(){
        let newContent = Note(context:viewContext)
        newContent.content = text
        
        do{
            try viewContext.save()
            
        }catch{
            fatalError("セーブに失敗")
        }

        nameFieldIsForcused = false

    }


}

struct Draft_Previews: PreviewProvider {
    static var previews: some View {
        Draft()
    }
}

[Swift] 19 メモアプリ製作 その5 iPadOS, macOS対応

[M1 Mac, Ventura 13.3.1, Xcode 14.3]

macOSとiPadOSでもビルドし、4つのApple製OSでメモアプリを使えるようにしました。CloudKitによりデータをiCloudで共有しています。

iOS、iPadOS、macOSではメモ表示・編集、watchOSではメモ表示ができます。

macOSを対象にビルドしたアプリは階層の深いところにあるため、起動後Dockに残してからFinderに表示させ、アプリケーションへコピーしました。

これで当初の目的はほぼ達成しました。ただし前にも書いたようにApple Developer登録していないとこのアプリ(のCloudKit)は使えないので、他社の安価なバックエンドサービスが使えないか調査します。

iPadOS版
macOS版

[Swift] 18 メモアプリ製作 その4 Apple Watch対応

[M1 Mac, Ventura 13.3.1, Xcode 14.3]

作成したメモアプリをApple Watchでも使えるようにしました。

最初からWatch App with iOS Appとしてプロジェクトを作成すると何故かCloudKitとの連携ができなかったので、まずiOS Appとしてプロジェクトを作成しCloudKit連携させてからTargetにWatch Appを追加しました。

ただこの方法ではWatch Appの名前をiOS Appと同じにはできません。設定した名前の末尾に”Watch App”が追加されるので同じにしても問題はないはずなのに融通が利かないです。

これまではNotebookというメモアプリを使っていたのですが、Apple Watchに表示させるとメモ内容が薄くグレーアウトしていて見にくい仕様になっています。対して今回のアプリはグレーアウトもなくフォントサイズも大きめでだいぶ見やすくなりました。

XcodeのUIといいSwiftの言語仕様といい、相変わらずの使いにくさで愛着が生まれそうにありません。特にforEach文のinの後に置くべき反復変数を省略できる、というまぎらわしい仕様にはあきれました。C#のようにforeach(int num in numbers)にして欲しいです。inの後に続く単語が何も関係ないと知り、少しキレそうになりました。Flutterで作ったらこのようなストレスとは無縁になるのでしょうか。

次はコンプリケーションに対応させて、文字盤からアプリを呼び出せるようにします。

watchOS版 実機
iOS版
import SwiftUI
import CoreData

struct ContentView: View {
    @Environment(\.managedObjectContext)var viewContext

    @FetchRequest(sortDescriptors:[])
    var contents: FetchedResults<Note>

    var body: some View {
        NavigationView{
            List{
                ForEach(contents){content in
                    NavigationLink{
                        DraftAppleWatch()
                    }label:{
                        Text(content.content!)
                    }
                }
                .onDelete(perform:deleteContent)
            }
            .navigationTitle("メモリスト")
            .navigationBarTitleDisplayMode(.inline)
        }
    }
    
    func deleteContent(offsets:IndexSet){
        for offset in offsets{
            viewContext.delete(contents[offset])
        }
            
        do{
            try viewContext.save()
        }catch{
            fatalError("セーブに失敗")
        }
    }

}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

[Python] 356 HEICからPNGへの変換 iPhone画像 / HEIC2PNG

[M1 Mac, Ventura 13.3.1, Python 3.10.4]

iPhoneで撮影した画像はHEIC形式になります。

これをPNGファイルに変換するにはpyheifライブラリを使うのがメジャーのようですが、私の環境ではエラーが発生します。

そこで昨年9月にリリースされたheic2pngライブラリを使って変換しました。

pip install HEIC2PNG
from heic2png import HEIC2PNG

if __name__ == '__main__':
    img = HEIC2PNG('test.HEIC')
    img.save() # test.pngに変換

[Swift] 17 メモアプリ製作 その3 CloudKit実装

[M1 Mac, Ventura 13.3.1, Xcode 14.3]

前回の記事で紹介したAmazonの電子書籍を読了しました(Kindle Unlimited)。

まだ不完全ながらメモアプリが一応完成しました。

あとは独力で機能を充実させていきます。

import SwiftUI
import CoreData

struct ContentView: View {
    @Environment(\.managedObjectContext)var viewContext

    @FetchRequest(sortDescriptors:[])
    var contents: FetchedResults<Note>

    var body: some View {
        NavigationView{
            List{
                ForEach(contents){content in
                    NavigationLink{
                        if((content.content?.isEmpty) == false){
                            if((content.content?.isEmpty) == false){
                                Draft(text:content.content!, note: content)
                            }
                        }
                    }label:{
                        if((content.content?.isEmpty) == false){
                            Text(content.content!)
                        }
                    }
                }
                .onDelete(perform:deleteContent)
            }
            .navigationTitle("リスト")
            .navigationBarTitleDisplayMode(.inline)
            .toolbar{
                ToolbarItem(placement:.navigationBarLeading){
                    EditButton()
                }
                ToolbarItem(placement:.navigationBarTrailing){
                    NavigationLink{
                        Draft()
                    }label:{
                        Text("+")
                    }
                }
            }

        }
    }
    func deleteContent(offsets:IndexSet){
        for offset in offsets{
            viewContext.delete(contents[offset])
        }
            
        do{
            try viewContext.save()
        }catch{
            fatalError("セーブに失敗")
        }
    }

}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

[Swift] 16 メモアプリ製作 その2 CloudKit

[M1 Mac, Ventura 13.3.1, Xcode 14.3]

CoreDataやCloudKitが上手く使えず途方に暮れていたところ、Amazonに個人出版のいい電子書籍を見つけました。

参考書籍

ただCloudKitはApple Developer登録しないと使えません(12,980円/年)。とりあえず半年ぶりに登録しましたが、iOS, iPadOS, watchOS, macOSのクロスプラットフォームなメモアプリを作ったところで、登録が切れると使えなくなります。これには最初の意気込みもトーンダウンです。

このシリーズ記事はそれなりに長く続けるつもりでしたが、あと数回で最終回になるかもしれません。AWSなど他社のバックエンドサービスが安価で使えないか、調査を進めていきます。

ところで、Swiftの命名規則はキャメルケース(単語間を直接つなげる)です。さすがに4単語、5単語連結になってくると可読性が著しく低下しますね。まあこれも慣れでしょうか。

import SwiftUI

struct ContentView: View {
    var body: some View {
        NavigationView{
            List{
                Text("Hello,world!")
                Text("Hello,world!")
            }
            .navigationTitle("リスト")
            .navigationBarTitleDisplayMode(.inline)
            .toolbar{
                ToolbarItem(placement:.navigationBarLeading){
                    EditButton()
                }
                ToolbarItem(placement:.navigationBarTrailing){
                    NavigationLink{
                        Draft()
                    }label:{
                        Text("+")
                    }
                }
            }

        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

[Swift] 15 メモアプリ製作 その1

[M1 Mac, Ventura 13.3.1, Xcode 14.3]

iOS, watchOS, iPadOS, macOSで使えるメモアプリの製作に取り掛かりました。

とりあえず叩き台のコードをChatGPTに作成してもらいました。

あっさり作ってくれてちょっと引いています。

非IDEにこだわらなければ、Xcodeにどっぷり依存で簡単なアプリを量産できそうです。

import SwiftUI

struct Memo: Identifiable {
    let id = UUID()
    let title: String
    let content: String
}

struct MemoListView: View {
    @State var memos: [Memo] = [
        Memo(title: "メモ1", content: "これはメモ1です。"),
        Memo(title: "メモ2", content: "これはメモ2です。"),
        Memo(title: "メモ3", content: "これはメモ3です。")
    ]
    
    var body: some View {
        NavigationView {
            List(memos) { memo in
                NavigationLink(destination: MemoDetailView(memo: memo)) {
                    Text(memo.title)
                }
            }
            .navigationBarTitle("メモ一覧")
            .navigationBarItems(trailing: NavigationLink(destination: MemoEditView(memos: $memos)) {
                Image(systemName: "plus")
            })
        }
    }
}

struct MemoDetailView: View {
    let memo: Memo
    
    var body: some View {
        VStack {
            Text(memo.title)
                .font(.title)
            Text(memo.content)
                .padding()
            Spacer()
        }
        .navigationBarTitle(memo.title)
    }
}

struct MemoEditView: View {
    @Binding var memos: [Memo]
    @State var title: String = ""
    @State var content: String = ""
    
    var body: some View {
        Form {
            Section(header: Text("タイトル")) {
                TextField("タイトルを入力してください", text: $title)
            }
            Section(header: Text("内容")) {
                TextEditor(text: $content)
            }
            Section {
                Button(action: {
                    let memo = Memo(title: title, content: content)
                    memos.append(memo)
                }) {
                    Text("保存")
                }
            }
        }
        .navigationBarTitle("新規メモ")
    }
}