[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

[AI] ChatRWKVアプリ製作 その11 M1 Mac / Metalで再挑戦

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

ChatRWKVの設定により7Bサイズのモデルを読み込めるようになりました。

環境変数 PYTORCH_MPS_HIGH_WATERMARK_RATIOをゼロに設定しメモリ使用の上限を撤廃することで読込が可能になります。ただしシステムが不安定になる可能性があるため要注意です。

動作はかなり遅く実用にはほど遠いですが、RAMメモリ強化でどこまで速くなるのか興味深いです。

ところでAppleのイベントWWDCが今晩開催されますが(日本時間6/6 午前2時)、AIについてどのような発信があるのか楽しみです。

またグランフロント大阪にApple Storeを出店する計画があるとか。実現したらヨドバシ梅田と同様に売り上げ日本一になるのでは。

import os

# PyTorch環境変数設定
os.environ['PYTORCH_MPS_HIGH_WATERMARK_RATIO'] = '0.0'
print(f'PYTORCH_MPS_HIGH_WATERMARK_RATIO = {os.environ["PYTORCH_MPS_HIGH_WATERMARK_RATIO"]}\n')
self.stratChoice = QComboBox(self)
self.stratChoice.setGeometry(215,70,100,25)
self.stratChoice.addItem('mps fp32 -> cpu fp32 *10')
self.stratChoice.addItem('mps fp32')
self.stratChoice.addItem('cpu fp32')
self.stratChoice.addItem('cuda fp16')
7Bでは環境変数を変更しないとエラーになる
環境変数変更で7Bは読み込めたが動作はかなり遅い