[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("新規メモ")
    }
}

[AI] GPT 0613版リリース, 関数呼び出し機能 Function API追加

GPT-4とGPT3.5の0613版がリリースされました。この最新版で関数呼び出し機能 Function APIが追加されました。

天気APIを使ったコード例がネット記事に掲載されていたので、早速内容をチェックしました。

参考記事

GPT 0613版 Function APIのフロー

ChatGPTに指示文とfunctionsパラメータ、function_callパラメータを送信する。

ChatGPTが必要と判断したらfunction名と引数を返信する。

利用者側はこれを受け、function名と引数をパラメータとして取込み再度送信する。

functionの戻り値を組み込んだレスポンスを返す。

天気APIを利用する場合

“東京の現在の天気を教えてください。”と送信する。(functionsパラメータ他もバックグラウンドで同時に送信)

<コードの一部>
response = openai.ChatCompletion.create(
model="gpt-4-0613",
messages=[{"role": "user", "content": text}],
functions=[weather_function],
function_call="auto",
)


ChatGPTはweather_functionの使用が必要と判断し、引数(緯度、経度)とともに返信。

利用者側はweather_functionの使用を指示するプロンプトを自動送信。

ChatGPTは天気APIから得られた情報を元に返信。
“東京の現在の天気は20.9℃で、風速は4.2 m/s、風向きは北北西329.0度です。天候コードは2で、おおむね晴れています。ただし、現在は夜間です。”

OpenAI社にあまり課金していないのでGPT-4 APIやChatGPT plugins APIを使わせてもらえない身ではありますが、これでプラグインと同等の機能を自製できるようになりました。

開発中のChatGPTアプリにも取り入れたいと思います。

[AI] ChatRWKVアプリ製作 その12 Mac版メモリ制限解除

[M1 Mac, Ventura 13.3.1, Python 3.10.4, PyTorch 2.0.0]

Mac版を改良しメモリの制限を解除できるようにしました。

def loadModel(self):
    self.box.setStyleSheet('background-color: #3E62AD')
    self.output.setText("")
    
    # PyTorch環境変数設定
    limitoffChecked = self.limit.isChecked()
    
    if limitoffChecked == True:
        os.environ['PYTORCH_MPS_HIGH_WATERMARK_RATIO'] = '0.0'
    else:
        try:
            del os.environ['PYTORCH_MPS_HIGH_WATERMARK_RATIO']
        except:
            pass

    try:
        print(f'PYTORCH_MPS_HIGH_WATERMARK_RATIO = {os.environ["PYTORCH_MPS_HIGH_WATERMARK_RATIO"]}\n')
    except:
        print('PYTORCH_MPS_HIGH_WATERMARK_RATIOは設定なし\n')

<以下略>

[Python] 355 OS名、バージョン、RAMメモリサイズを取得

[M1 Mac, Ventura 13.3.1, Python 3.10.4]

Macの場合

import platform
import psutil

# OS名を取得
os_type = platform.system()

if os_type == "Darwin":
    os_type = "Mac"
    
    # OSバージョンを取得
    os_version = platform.mac_ver()[0]
    
    if os_version.startswith("13"):
        ver_name = "Ventura"
    
print("OS:", os_type)
print("OSバージョン:", os_version)
print("バージョン名:", ver_name)

# RAMメモリを取得
ram = psutil.virtual_memory().total / (1024 ** 3)
print("RAMメモリ:", round(ram, 2), "GB")
OS: Mac
OSバージョン: 13.3.1
バージョン名: Ventura
RAMメモリ: 8.0 GB