在 App Store 上架了第一款软件

在去年的年终总结里,我定下的 2024 年目标之一就是在应用商店上架一款软件。作为一名软件工程师,做出来一个自己的软件并上架到应用商店,是一种无形的荣誉。

在 App Store 上架了第一款软件
Photo by Mariia Shalabaieva / Unsplash

在去年的年终总结里,我定下的 2024 年目标之一就是在应用商店上架一款软件。作为一名软件工程师,做出来一个自己的软件并上架到应用商店,是一种无形的荣誉。

在 2024 年 1 月 1 日,我订阅了 Apple Developer Program,将刚开始不久的项目连接到 App Store Connect

11 月 27 日,我的软件正式版审核通过,被批准上架 App Store。

开发和上架历程

软件创意

Float Translator (以下简称 FT) 是一个 macOS 翻译软件,支持输入翻译和划词翻译。做这个产品的初衷是解决快速翻译外文资料中的单词短句的需要。基础功能实现起来不难,我花了很短的时间就做出了雏形。后来想到自己有一个上架软件的目标,就不停地优化软件的功能和设计,为上架做准备。

市面上有不少类似的产品,比如好评如潮的 Bob ——我和它的作者还是前同事。我并不渴望某一天 FT能和这些优秀的软件分庭抗礼,也不想着能靠它挣到钱。我只是希望它能在学习工作中帮到我和用户。

开发历程

从开始做 FT,到上架,历时近一年时间。这倒不是技术实现困难,耗时点在于可投入的时间少,设计和审核时间长,以及自己的懒惰。

我有本职的工作,并不是一个独立开发者。上班时根本没有时间和机会去做自己的项目,只能下班后或者周末有空余时间了才会去推进。再加上周末往往会有各种事情,比如去周边城市旅游、朋友聚餐等,自己的拖延症,所以能投入到 FT 项目的时间少之又少。

整个 FT 项目的推进流程是这样的——初版 UI 设计,实现基础翻译功能,实现快捷键功能,支持切换语言,实现划词翻译,优化网络速度,上架 TestFlight 内测,支持存储翻译历史,「设置」和「关于」页面设计重构,用户引导,应用内购,尝试上架 App Store,不停地解决审核问题。前面流程的一些功能实现和界面设计的难度稍小。应用内购和上架 App Store 对我而言比较难,因为我并没有接触过内购和上架,需要不停地学习和改进。

上架历程

一款应用上架前最好先走几遍 TestFlight 内测和外测。内测就是开发者团队内部的测试,需要账户管理员将内测测试员添加到团队里,安上一个职务(比如销售)。外测是比较正式的测试版了,非开发者团队的人员也能通过 TestFlight 下载你的测试版软件。内测基本上不咋审核,外测审核严一些。但外测也不会像正式上架的审核那样,把软件里里外外翻一遍。

我倒是希望外测能再审核严一些,这样就能早一点发现上架的问题了。比如我的软件名字问题——我的 FT 一开始叫 Pocketize,中文含义大概是口袋翻译(一开始备案也是按这个备的),是我随意起的,最初的软件图标也设计成了一个口袋样式。我应该一早就想到它可能会和很多软件或产品有名字冲突的。直到上架的时候,审核员才告诉我,我不能用这个名字,存在「Guideline 4.1 - Design - Copycats」 问题。我不得不在 Xcode 和 App Store Connect 中修改软件的名字(注意不要改 bundle identifier),重新设计软件的图标。一来一回,耽误了不少时间。

对于需要注册登录的软件,开发者需要向审核员提供测试账号。FT 不需要注册登录,也就不需要提供。某些软件公司会提供一个特殊的测试账号,登录后会展示特意给审核员看的内容,以此规避某些审核问题。

像我这样初次接触 App Store 审核的人,一定会对审核员的各种奇葩问题感到惊讶和无奈。下面是我遇到的一些审核问题。

应用商店审核记录

Entitlements 存在一个或多个用不到的功能

我的 entitlements 设置了一个用不到的 「com.apple.security.network.server」,删掉就行了。违反的规则是「Guideline 2.4.5(i) - Performance」。

会员功能描述不清晰

Your app uses auto-renewable subscriptions, but it does not clearly describe what the user will receive for the price.

这个改起来也比较简单,在付费墙页面注明成为会员后拥有哪些权益就好了。审核员会贴心地告诉你,付费墙需要展示哪些内容。

违反的规则是「Guideline 3.1.2 - Business - Payments - Subscriptions」。

应用元数据缺少 EULA

The app metadata must also include functional links to the privacy policy and Terms of Use (EULA).

我以为在付费墙上面贴上隐私政策链接就好了。但是审核员说还缺少 Term of Use (EULA) 链接。这个链接使用官方提供的就可以,你只需要把它放在显眼的位置——付费墙和应用描述里。

我在这个问题上卡了很久,提交了多个版本都被打回了。最开始并不知道需要把 EULA 链接放哪,后来看了网上前辈们的攻略才知道需要把链接放在应用描述里。

违反的规则也是「Guideline 3.1.2 - Business - Payments - Subscriptions」。

技术支持链接不合格

The Support URL provided in App Store Connect does not direct to a website with information users can use to ask questions and request support.

我在 App Store Connect 上面放置的技术支持链接指向的是软件的官网,虽然上面也标注了我的邮箱和其他的联系方式(图标形式),但 Apple 认为这并不是一个合格的技术支持网站,用户不能看到有效的联系信息,也无法在网站上留言。

后来我专门写了一个简单的技术支持网页,注明了我的邮箱和电话号码,并且还贴了我的博客页面,博客页面是可以通过评论联系我的。这样就通过了这一项的审核。

违反的规则是「Guideline 1.5 - Safety」。

软件名字和其他流行的软件有相似的地方

Your app's metadata contains content that is similar to third-party content, which may create a misleading association with another developer's app or intellectual property.
Specifically, your app's name includes references to PocketSize.

违反的规则是「Guideline 4.1 - Design - Copycats」。

上面也提到了,FT 一开始叫 Pocketize。审核员说 Pocketize 和别的软件或者第三方品牌有冲突,而且还举了个例子——PocketSize……

我在应用商店里搜索 Pocket,确实能搜到很多名字类似的软件,而且有一个还和我的软件图标很相似,都用口袋作为图标主体,巧得很。所以我不得不更换软件名字,重新设计图标。起名字真的是一件很费神的事情。

总结

做一款软件是一个持续学习的过程

你会学习到各种知识,包括技术、设计和营销。

完整地做一个软件,是和工作中做一个业务需求有很大差别的。在公司里,尤其是大厂,做一个业务需求或者技术需求,用到的技术知识基本上是自己已经熟悉掌握的,很少有机会接触到新技术。某些比较保守的公司,还会限制你使用新技术,让你只能用老旧的技术缝缝补补。而自己做一个软件,会接触到各种新知识,遇到很多新难题。你可以随心所欲用新技术,没有人会约束你,Apple 甚至鼓励你在软件里应用新技术。

在这个过程中,你会慢慢发现,设计和营销对于软件也很重要。用户交互不友好的设计,吸引不到新用户的营销,会让你的劳动变成一场空。如果你想要成为一个独立开发者,或者想创业,那么就不要把技术看得那么重。做技术的人往往会有一种错觉,认为自己技术非常厉害,做出来的产品也会备受欢迎。但一款产品的成功,很少是因为技术,而是依靠出色的产品设计、优秀的用户交互体验以及能不断吸引新用户留住老用户的营销。

选好方向,做好调研

在你想要做自己的软件之前,先做好调研。至少回答「独立开发者的三问」中的核心问题:

  1. Who are customers? 用户群体是那些。
  2. What’s the problem? 解决现有用户的什么问题?
  3. What’s the solution? 一句话说明你的解决方案。
「独立开发者的三问」—— YouTuber 李自然说的视频 https://www.youtube.com/watch?v=u6mVi_wZs5E

调研充分,方向正确,是一个软件成功的基石。

个人项目也需要项目管理

软件研发不是一个短期就能完成的任务,是一个需要长期管理的项目。做好项目管理,一方面能让目标实现起来更加清晰,另一方面也能提醒自己有哪些任务待完成、任务的上下文是什么。

我目前用 Notion 做个人项目管理,开发过程中的每个功能细节都被拆分成了若干任务。如果内测或外测时发现了 bug,就在 GitHub 上给自己提一个 issue。

先设计再开发

在开发前,一定先把设计稿定下来,哪怕是随便画的。这样开发时才知道自己要做一个什么样的界面,会在心中有一个目标。没有设计稿的开发,就像是用代码在画草图,会导致你一遍又一遍推翻之前的代码,做许多无用功。

先上架外服,再上架国服

在国服商店提交审核前需要走 app 备案,备案需要提供软件的名字、介绍以及官网链接等信息。而这些信息可能会被应用商店审核员指出不合规,需要你修改。修改后,你的备案信息也需要进行同步变更。所以不如一开始先不上国服,等在外服改完问题、被批准上架后,再在国服走备案和上架流程。

Apple 平台非常适合个人开发者

Apple 向开发者提供了很多工具和学习平台,比如 Developer 软件、App Store Connect、CloudKit、SF 符号、各种设计模板等。可以说是给开发者准备了一切必要的工具,只等你实现自己的 idea 了。

新产品在路上了

FT 上线后,我准备将精力放在下一款产品中了。下一个会是个移动端软件,想法已经有了,准备开始产品设计。期待明年能在 App Store 中上架。

Read more

SwiftUI|监测视图更新及更新原因

SwiftUI|监测视图更新及更新原因

SwiftUI 的优势之一就是可以自动让 UI 和数据状态保持同步。频繁的视图更新也会带来一定的性能问题,尽管 Apple 已经让 SwiftUI 在更新时尽量只渲染视图树中必须重绘的部分,但我们还是需要知道某种可以监测视图更新以及触发其更新的原因的手段。这样我们才能在业务层面做一些针对性的性能优化。 我们可以通过在 View 的 body 视图构造器中插入一条 print 语句来监测到哪些视图的 body 正在被执行。这条语句还必须是一个赋值语句,不能是仅一个 print。因为 print 返回的是一个 Void ,而不是一个 View ,视图构造器会报错。 struct ContentView: View { var body: some View { let _ = print("ContentView Changed!") VStack { Image(systemName: "globe"

By Gray
天津一日行

天津一日行

五月份周末抽了一天去了天津转转。说来惭愧,在北京待了那么多年,我竟然一次都没有去过天津。 我们买的高铁票是北京南到天津站,游玩也是选的天津站附近的景点。当天天气不咋滴,上午阴天,下午就下起了雨。 出地铁后,映入眼帘的就是海河以及解放桥。 经过解放桥之后,再往前走一段路,就能看到路边有一个卖煎饼果子的小摊。我们当时刚下高铁,肚子正饿,所幸就买了一个吃。额外加了鸡蛋和辣条。都说天津的煎饼果子是一绝,这吃起来确实挺香👍。 穿过金街,一座看着很有时代感的建筑就映入眼帘——天津劝业场。刚看到它的名字时,我还以为是我念错了,怎么会有这么古怪的名字。后来一查还真是叫这个名字。 “劝业”二字也寄托着国人实业图强的希望。所以在天津劝业场开业之初,其经营宗旨以“劝业商场”四字的字头写出四句警言。即:劝吾胞兴,业精于勤,商务发达,场益增新。– 看天津系列:传统核心商圈金街 可能是因为当天是周六且非节假日,这条步行街上的行人并不多。游客们忙着拍照,本地人闲逛,和北京商业街的繁忙是不可同日而语的。天津是出了名的慢节奏,我倒是很喜欢这样的氛围。 做天津攻略时,我就看到有文章说天津美食有三绝:狗不理

By Gray