cover

背景

平时空闲时间,乱七八糟的想法比较多,有时候希望能够快速记录一下当时的想法,每次拿手机打字感觉挺麻烦的,就想有个录音软件,但是系统自带的录音软件不支持iCloud同步,就感觉挺鸡肋的。然后就去Apple Store搜了下,找到了一个合适的APP,不过这玩意居然卖38 RMB,难以理解。当初搞APP开发的时候,我估计3天就能撸一个现成的。巅峰时期,心流状态下,帮一个朋友做APP 估的一个月的工作量,国庆5天就做完了。

现在虽然7-8年没搞过APP开发了,加上iOS开发的技术栈都迭代几轮了。不过我觉得在AI加持下写个录音App应该So Easy。还有生成图片的大模型,UI设计我都不用去麻烦别人。

功能拆解

功能需求一句话总结:我就是要一个录音App,能方便的在我的iPhoneApple Watch上录音,能够进行iCloud同步,能够分享给其他人。

UI/UX

最开始想法很简单,我跟AI说下我要做个什么APP有什么功能,AI帮我把所有的UI都生成好。网上搜了下,相关的产品还不少随便贴一个 15 AI tools every UI/UX designer must try

试了一圈下来,稍微靠谱的不是很多,试用下来完成度比较高的是 uizard,但是生成的交互图太丑,如下图:

image.png

经过不断地优化调试Prompt,我最终放弃了AI生成交互的这条路。主要感受:

  1. 图片生成速度太慢(算力),一张图片平均下来5~8s,这东西跟抽盲盒,不可能一次就满意,不满意来回换太耗时。也不支持细节调整。
  2. 做为用户我也很难通过一段Prompt非常清楚的描述出自己要什么,我只能通过录音简约这些抽象的词来告知AI我大致的需求。理想态是,AI能一下生成10或者100套来给用户选。
  3. 总体感受,AI for UI目前最多只是能做个辅助(说辅助估计UI&UX设计师都觉得高看它了),要想把所有的UI&UX工作交给AI来做基本不可能。

图标

想着UI这边比较复杂的功能AI表现不行,那简单的logo、按钮图片应该没啥问题吧。国内支持生成图片的大模型还挺多的了。比如 豆包文心一言 都支持。不过 豆包文心一言 生成的图片都有水印,所以直接被我Pass了。

AI图片生成的网站挺多的,随便找了一些试用了下,跟上面生成UI一样,虽然能生成图片,但是跟自己想要的天差地别。也不支持生成的图片微调,总的来说要生成一个让自己的满意的图片挺难的。

再就是一个APP里面的所有的按钮图标,风格需要保持一致,但是这个也不支持生成套图。所以最后只是把APP Logo 这个工作外包给了AIAPP内的各种图片图标还是用了苹果官方自带的一套图标库 SF Symbols ,虽然很简陋,但是整体风格也会跟统一一些。

贴下AI生成的一些图片:
image.png

画UI

以前我做iOS开发的时候主要是用的Objective-C来开发,当初主要是MVC的模式,我一般都是在Controller里面写画UI的代码。demo如下:

@property (nonatomic,assign) int count;
@property (nonatomic,strong) UIButton *btn;


- (void)viewDidLoad
{
    [super viewDidLoad];
    _btn = [UIButton buttonWithType:UIButtonTypeCustom];
    _btn.frame = CGRectMake(100,100,100,100);
    [_btn setTitle:[NSString stringWithFormat:@"%d", _count] forState:UIControlStateNormal];
    [_btn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
    [_btn addTarget:self action:@selector(btnClick) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:_btn];
}

- (void)btnClick
{
    self.count += 1;
    [_btn setTitle:[NSString stringWithFormat:@"%d", _count] forState:UIControlStateNormal];
}

还有一种用的StoreBoard,可以通过通过拖拽控件的方式来画UI效果如下:

image.png

不过这2019年苹果推出了一个新的画UI的技术:SwiftUI,与早期的UIKit(上面介绍的两种都是UIKite模式)不同,SwiftUI使用声明式语法,这使得构建复杂界面和动画更加直观和简洁。支持实时预览,修改代码所见即所得(实际上大部分时候UI刷新比较慢)。SwiftUI非常适合于实现MVVM(Model-View-ViewModel)设计模式。

struct ContentView: View {
    @State private var count = 0

    var body: some View {
        VStack {
            Text("Count: \(count)")
                .font(.largeTitle)
            Button(action: {
                count += 1
            }) {
                Text("Increase")
            }
            .padding()
        }
    }
}

这个例子展示了如何使用@State管理状态,并将其绑定到视图上。当按钮被点击时,计数器增加,视图也会相应地更新。相比上面的UIKit的方式会简单直接很多。变量count跟文本控件绑定了,修改count就等于修改了Text上显示的内容,的确是方便了很多。

虽然我没有学习过Swift也不知道SwiftUI任何基本用法,在AI的加持下我觉得还是用SwiftUI来画个界面问题不大。实际上AI给了我很大的惊喜,结合gpt4o的多模态的能力,我看到其他APP好的交互形式,我直接可以截图发送给UI,让他们帮我生成SwiftUI,大多时候它都能帮我正确的识别出截图中字体颜色控件等等,大大的提升了我在画界面时候的开发效率。 感觉AI在生成这种胶水代码方面真的是大有可为

image.png

但是在一些复杂的交互场景,AI局限性还是体现出来了,比如我要实现一个根据录音时候说话声音大小来展示的声波图,调教了很久,AI始终不能给我一个WorkDemo出来。所以这个功能我还是放弃使用AI了,最后自己实现。

我需要实现的声波图

image.png

iCloud 云同步

iCloud支持三种类型数据同步:

  1. Key-Value Storage,是iCloud提供的一种简单存储机制,用于存储少量数据,如用户设置和偏好。每个键值对都会同步到用户的所有设备上。
  2. iCloud DocumentsiCloud Documents是用于在用户的所有设备上存储和共享文档的服务。它允许应用程序存储用户生成的内容并在所有设备上保持同步。
  3. CloudKitCloudKitiCloud提供的高级云存储服务,用于存储结构化数据。它可以处理更复杂的数据模型和大规模数据同步需求,是一种更强大的解决方案。(看是很高大上,其实我感觉没啥用,我要云存储,我自己起个Server,存我自己DB不就行了,为毛要存你iCloud, 官方CloudKit文档晦涩难度,API使用麻烦的要命,强烈不推荐用这玩意)

要调试iCloud这功能,居然还要开个苹果开发者资格才行(668RMB),这一点苹果不厚道。

image.png

我跟AI说了下,我要做iCloud文件同步的能力,AI很快就给我一个可以用的代码:

func saveFileToICloud() {
    // 获取iCloud文档目录的URL
    guard let iCloudURL = FileManager.default.url(forUbiquityContainerIdentifier: nil)?.appendingPathComponent("Documents") else {
        print("iCloud is not available")
        return
    }

    // 创建文本文件内容
    let fileName = "example.txt"
    let fileContent = "Hello, iCloud!"

    // 构建文件路径
    let fileURL = iCloudURL.appendingPathComponent(fileName)
    
    // 将内容写入文件
    do {
        try fileContent.write(to: fileURL, atomically: true, encoding: .utf8)
        print("File saved to iCloud")
    } catch {
        print("Failed to save file: \(error.localizedDescription)")
    }
}

代码看出,如果只要用iCloud的文件同步能力,接口很简单,只用使用FileManager.default.url(forUbiquityContainerIdentifier: nil)去拿文件路径就行了,把文件写到这个路径下,iOS系统会去帮你做同步和状态管理,对于程序员来说,就跟读写本地文件一样。离线都可以正常读写,这点要夸下苹果,封装的好,RD心智负担小。

上面这个文件虽然保存在iCloud里面了,但是我还想在MaciOSICloud文件管理里面能够看到这个文件夹。这么小的一个功能点,我跟AI来来回回交流了10几轮,都没搞定。最好还是自己Google搜了下,然后根据搜到的一些关键字,再加上我具体的意图,AI总于给了我一个可用的答案:

在项目的info.plist里面添加上这些配置就好了。

<key>NSUbiquitousContainers</key>
<dict>
    <key>iCloud.fanlv.fun.Melody</key>
    <dict>
        <key>NSUbiquitousContainerIsDocumentScopePublic</key>
        <true/>
        <key>NSUbiquitousContainerName</key>
        <string>Melody</string>
        <key>NSUbiquitousContainerSupportedFolderLevels</key>
        <string>Any</string>
    </dict>
</dict>

这个了这个配置以后,在iCloud里面App存到iCloud的文件夹就能显示出来了。

WechatIMG195.jpg

ShortCut && Siri

iOS的快捷指令(ShortCut),是一个很强大的功能,能够通知指令调度编排串联iOS上各种APP的各种能力,老互联人玩过IFTTT的应该都不陌生。

举几个例子,我可以实现一个截屏翻译ShortCut,他可以给我一键截屏 -> OCR识别图片文字 -> 然后帮我翻译 -> 显示翻译的结果。

image.png

更高级一点玩法,可以跟智能家居联动,比如我指纹开门以后,自动打开我房间的灯/空调。

有了ShortCut,就可以通过Siri来唤醒和调用ShortCut对应的能力。这样的好处是,我能通过我iPhone或者AppleWatchSiri来调用ShortCut。比如控制家里任何智能家居或者执行各种复杂的APP功能。

在这里我主要想实现一个简单功能:“通过Siri唤醒APP然后开始录音”,就这么简单一个功能,让我跟AI从周六晚上6点一直纠缠到了凌晨1点(话说写代码/搬砖 还是很容易进入心流模式的)。

我跟AI说我要在我的APP识别我是不是已经添加过这个快捷指令了,添加了就显示已经添加,没有添加就显示点击按钮添加到Siri

image.png

AI这厮一直跟我说苹果因为隐私原因没有提供查询所有快捷指令的API,不行最后还是各种Google搜关键字,然后结合别人给的一些接口,再去问AI,期间因为各种细节问题调了半天,最后实现其实很简单,核心代码如下:

     // 添加 Shortcut 指令核心代码:
     let activity = NSUserActivity(activityType: "LaunchMelodyAppIntent")
    activity.title = NSLocalizedString("oneKeyRecord", comment: "")
    activity.userInfo = ["fan": "lv"]
    activity.isEligibleForSearch = true
    activity.isEligibleForPrediction = true
    let shortCut = INShortcut(userActivity: activity)


     // 判断是否添加过这个指令核心代码:
    INVoiceShortcutCenter.shared.getAllVoiceShortcuts { (shortcuts, error) in
        guard error == nil else {
            return
        }
        if let shortcuts = shortcuts {
            for voiceShortcut in shortcuts {
                if let userActivity = voiceShortcut.shortcut.userActivity,
                   let userInfo = userActivity.userInfo,
                   let fan = userInfo["fan"] as? String {
                    if fan == "lv" {
                        isAlreadyAddToSiri = true
                    }
                }
            }
        }
    }

参考了下这个方案 iOS-自定义Intent及ShortCut,能通过快捷指令唤醒APP并跳转到指定页面

DynamicIsland(灵动岛)

灵动岛功能实现相对较简单,AI基本说的跟苹果官网的 Displaying live data with Live Activities 这个差不多。

也没费太多力气,唯一一点需要注意的就是动态更新的话需要在宿主APP里面写代码定时更新灵动岛的数据,实现如下:

timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] timer in
    for activity in Activity<MelodyActivityAttributes>.activities where activity.id == self.activityID {
        Task {
            await activity.update(using: MelodyActivityAttributes.ContentState(timerLabel:self.recordSecond.toSimpleRecordTimeString()))
        }
    }
}

具体实现效果如下:

WechatIMG208.jpg

Apple Watch App

Apple Watch App功能基本是iOS App的阉割版,大部分代码都可以复用,唯一要说的就是,要想ShortCut能在Apple Watch上运行,也要新建一个。

image.png

image.png

不过也要吐槽一下iOSApple Watch共享代码的实现方法太ugly了,在把对应的源码文件所属的项目,勾选下WatchApp,在WatchApp工程里面就可以用了,完全没有模块的概念。

image.png

总结

  • UI&UX,目前想要AI来生成交互图,这个跟抽盲盒一样,可用性极低,还任重道远。
  • UI代码生成,gpt4o这一块能力有点惊艳到我,我想“借鉴“一些App设计,给他一个截图,很快就能给我生成相关的UI代码,可用性极高,基本贴过来,调整下细节就可以了。
  • 新功能调试,像Shortcut灵动岛这种新功能,由于我之前没有开发过的,所以问的问题可能比较抽象(比如我要一个 xx 功能),这可能会让你在一个死胡同里面,跟AI花费大量时间来回拉扯,这个其实挺痛苦的。需要自己扩宽一下思路(Google了解基本的实现方式),再输入具体信息再来问,你又会发现柳暗花明又一村。

总的来说,AI已经全面改变我的学习、生活方式了。想想当初学习新的技术时候,有AI的话一定可以事半功倍。还有当时搞什么技术讨论群,大家在群里为了一些技术问题争论的面红耳赤,有时候甚至会上升到人生攻击。现在有了AI,可以大大的减少一些技术上毫无意义的争论。

随着算法和算力的不断优化和提升,相信AI以后还能有更大的突破。不拥抱变化,就要被变化淘汰。