Hi there 👋

Welcome to cloxnu’s creative space.

四年之后,我选择和自由一起出发

今天是七夕,是跟女朋友约会的日子,也是我职业生涯的 last day。 这两件事,说来你可能觉得八竿子打不着,但机缘巧合,我们共同合作的一款独立开发的 App 「FocusFlight」,却成为了连接一切的起点。 很多人可能觉得,独立开发是这两年才火起来的事。AI 兴起之后,谁都可以做点小产品。但我的故事要追溯到八年前。 大一的图书馆,我偶然翻到周楷雯的《Producter:让产品从0到1》。那一刻,我突然觉得: 如果能做出一个 App,全世界的人都能在手机上下载,该有多酷! 于是我组了一台黑苹果,从0到1开始边学边做自己的产品,尤其是当我听说 Apple 每年会评选一些设计优秀的 App 颁奖,叫做 Apple Design Awards 的时候,觉得得奖的那些 App 更酷了,简直就是 App 界的奥斯卡,那时的我眼里,别的什么都不重要。 直到大学毕业,才有一款小有名气的 App 出现了,叫「文字卡片」,也是我做的第一款可以拿到 Apple 每周编辑推荐的 App。 毕业后,我开始了我的打工生涯。 四年打工,我待过大厂(严格且无趣,每天加班到很晚),也待过小团队(自由且灵活,有更多趣味性工作)。区别是挺大,但相同点是:你始终是在完成别人的目标。 直到 「FocusFlight」 出现,它成了我所有经历的汇聚点。说它是 App,其实也是个借口。 借口是什么? 是我们在咖啡店、公园、海边、甚至酒吧,找了无数理由逃离格子间。 是我们证明“灵感不一定诞生在会议室里”,它也可以生长在海风和背景音乐里。 至少比被困在深南大道早高峰里,要浪漫多了。 这四年里,也不是没有迷茫的时候。 一方面觉得上班挺没劲的,另一方面又承认它确实很稳定。 直到有一天,我发现那些“不确定”慢慢变成了“可行”。 独立 App 虽然不是什么一夜暴富的故事,但它们足够让我不用再纠结“能不能活下去”,而是开始思考“要怎样活得更自由”。 于是我明白,自己已经走到下一个阶段。 那接下来,终于可以心安理得地去折腾梦想了。 未来见,在路上见。愿每一天都不只是工作日,而是心动日✨。...

August 29, 2025 · 1 min · Sidney Liu

iOS 上的屏幕截图隐藏 / 显示内容的研究

背景 在现代数字交流中,屏幕截图成为一种常见的工具,不仅用于记录和保存内容,还广泛用于分享信息。一篇题为《Why do people take Screenshots on their Smartphones?》1 的研究论文指出,有97%的受访者曾将截图发送给他人。这一数据表明,屏幕截图不仅仅是个人记录的手段,更是一种重要的社交互动方式。通过截图,用户能够快速分享对话、通知或有用的内容,从而在信息传播和交流中发挥关键作用。 然而,对于这样高比例的分享行为,用户可能的目的有: 认为当前屏幕的内容有趣,想要分享给其他人; 认为当前屏幕的内容不符合预期,想要反馈给开发者; 于是,我们可以从中做出这些事: 在用户截图时将品牌 logo 放置在不影响其他内容的地方,提高品牌宣传力; 将屏幕截图中的敏感信息隐藏起来,防止这些内容暴露给其他人,保护用户隐私; 在隐私协议允许范围内将诊断信息隐藏在截图中,从而更好地优化产品可能遇到的问题; 因此,接下来我们将开始研究实现方式。 使用 mask 隐藏 / 显示信息 本质上来讲,我们如果做到了可以在截屏时隐藏信息,那也就同样做到了显示信息,我们可以通过隐藏 / 显示蒙板的内容来控制实际内容的显示。对于 SwiftUI,有以下代码: content .mask { ZStack { Color.white HideWhenTakingScreenshot { Color.black } } .compositingGroup() .luminanceToAlpha() } 假设我们已经实现了 HideWhenTakingScreenshot 中的内容,在屏幕截图时隐藏 Color.black ,对于 content ,也就相当于在屏幕截图时显示了,UIKit 同理。 在屏幕截图时隐藏信息 经过层层过滤,其实我们的真实需求是如何在屏幕截图时隐藏信息。 UITextField 的神奇作用 说到隐私保护,我们不得不想起当用户在密码框中输入密码时作为 iOS 系统级的保护,他会在用户截屏时自动隐藏密码输入框,如以下代码所示: struct PasswordTextView: UIViewRepresentable { @Binding var password: String? func makeUIView(context: Context) -> UITextField { let textField = UITextField() textField....

July 16, 2024 · 6 min · Sidney Liu

「BACK 4U」上架啦🎉

App Store 链接 经过很长时间的设计、开发,「BACK 4U」终于上架啦🎉。...

February 10, 2023 · 1 min · Sidney Liu

Palette 1.1 发布

新版本的 Palette 发布了,这次有了中文标题和描述。 这次还增加了许多可配置的新 Block,其中主要包括一个 Color Mixing 的模块,其 Block 包含自己可以存储调色板的 Palette Block,和一些可供挑选颜色的 Block。 Palette on the App Store...

June 10, 2022 · 1 min · Sidney Liu

使用「快捷指令」配合「Taio」完成 Blog 写作并发布

可能是 Blog 发布流程太长太麻烦,导致我总是不太经常写 Blog,但这次我发现「快捷指令」可以把「Working Copy」的 Commit、Push、Pull 等操作集成起来,而且也可以使用 Taio 动作,于是行动起来。 在 Taio 动作中我写了两个动作,分别是创建 Post 和创建 News,在运行这个快捷指令时可以选择创建 News 还是 Post,如果是 Post,那还需要选择类别,类别列表通过「Taio」的获取目录可以获得现有的类别,否则也可以新建类别。 创建 Post 的动作可以接受一个输入参数,即类别。 这个打开链接就是打开根目录的 README.md 文件,后面的文件指令才能找到正确的路径。 运行 大功告成~ 剩下就是发 Blog 然后等着 GitHub Actions 去处理啦 把这几个 Blog 操作合成一个小组件,简单易用~...

June 1, 2022 · 1 min · Sidney Liu

Palette 1.0 上架 App Store

这是一款很随意的 app,但这也是一款可以用 iPad 制作出来并可维护的 app。 链接在这里 楔子 当我第一次吃惊地发现 iPad 居然可以用 Playground 写一个 app 并上架到 App Store 时,于是脑海里瞬间浮现出无数个想法,然后立马去试试。 那时我完全没有接触过 SwiftUI,但是在我尝试试着在 iPad 上写一些代码时,借助 Playground 里的 code snippet,我竟发现 SwiftUI 原来现在已经发展到可以如此便捷高效地构建一个 app。 然后我就决定写一个颜色转换再加上可以配色的超简洁的 app。 说起颜色转换,我不止一次开发跟颜色相关的项目,上次是在钟大的 JSBox 里写了一个颜色转换的 script。 几年前,我曾做过一个叫「今天的颜色」的 app,它的功能非常简单,就是生成一个颜色,每天一个不同的颜色,同时还会把 logo 改成当天的颜色。却也是因为功能太简单,被 App Store 拒绝上架。时到今日,today’s color 的 block 在这个 app 会在未来某个版本安排一下~纪念一下。 Just Do It 4 月末,我开始了这个项目,经历了十几个版本的测试,也就大概十几天,上架了 App Store,在前几天还因为 App Store Connect 的服务不稳定稍稍拖慢上架速度。 在 iPad 上开发过程中,还是给了我以下几点不方便的因素: 难以调试,只允许做一个非常简单的项目,一旦复杂起来遇到不好解决的 bug 就得去 mac 上调试 搜索功能不够强大 性能不够,遇到较复杂的语句就无法编译,有时候会导致整个 Playground 卡住(可能是 bug)(就算是 M1 的 iPad 也性能不够,跟 mac 上的 M1 应该是缩水了的) 无法做本地化 等等… iPad 上开发 app 还有许多需要改善的地方,但这样的思路的确是一个相当好的思路,试想一下如果未来的 iPad 可以允许我们每个人轻松做一个游戏然后上架,每个人都可以把自己的想象发挥到极致而无需担心实现的难度,Apple 官方提供一套无版权的人物角色模型,类似现在的 SF Symbol。下一个时代是人人创作者的时代。...

May 14, 2022 · 1 min · Sidney Liu

AA Cell 2.2.0 正式版发布!

在经过长时间的研究,这个版本改回了原来 1.x 的电池 logo,名称再次改为「5 号电池」(「AA Cell」) AA Cell - 文件装进来,电池丢过去 修复与更改 更改 logo,标语以及价值观 点击充电可将电池充满,不再需要付费 ...

April 25, 2022 · 1 min · Sidney Liu

Telegram Bot 简明教程 II - 收指令与指令键盘

在此之前 Telegram Bot 简明教程 I - 注册与发消息 收指令 python-telegram-bot wiki 页面 介绍了如何使用 Python 脚本实现与 Bot 交互。 以下是根据这个 wiki 页面编写的例程。 接收 /start 指令 from telegram import Update from telegram.ext import Updater, CallbackContext, CommandHandler token = '2110628450:AAHQ78uj42ddtdsx0gKfaZGyFUhpnQ13vyM' def start(update: Update, context: CallbackContext): context.bot.send_message(chat_id=update.effective_chat.id, text="Let's start!") # 或 # update.message.reply_text("Let's start!") if __name__ == '__main__': updater = Updater(token=token, use_context=True) #1 start_handler = CommandHandler('start', start) #2 updater.dispatcher.add_handler(start_handler) #3 updater.start_polling() #4 updater.idle() #5 首先根据 token 创建一个 updater 对象; 定义 start 函数,在函数体中实现给发指令的那个 chat_id 发送消息「Let’s start!...

November 17, 2021 · 2 min · Sidney Liu

Telegram Bot 简明教程 I - 注册与发消息

Telegram Bot,简而言之就是运行在 Telegram 上的可交互的「机器人」,你可以给它发送指令让它完成操作或是实现一些功能(付钱、游戏等等),或者可以在 Channel 或 Group 中发送特定消息。 这是 官方介绍。它的主要原理就是开发者通过调用 Telegram Bot API 来实现接收指令、发消息以及实现各种功能。 注册 与 @BotFather 对话,发送指令 /start 开始,/newbot 申请一个新的 Bot 账号。 接着,BotFather 会要求你输入这个 Bot 的名字和 ID。创建完成后,BotFather 会同时给你一个 token,记住这个 token。 此时,已经可以和这个 Bot 互动了,但是想要这个 Bot 也可以主动发消息,这时就要建立一个 Channel,并把这个 Bot 设置为管理员。这个 Channel 如果是 public,其链接可以自定义。这里以 private 为例。 由于 Channel 是 private,我们需要这个 Channel 的 ID 来操作,这里可以通过将 Channel 内的消息转发给 @JsonDumpBot 来查看。可以看到此 Channel 的 ID 是 -1001790411176。 发消息 在官方文档的 Making requests 介绍中讲到,可以使用 GET 或 POST 请求以下 URL。...

November 10, 2021 · 1 min · Sidney Liu

如何用 Git 命令修改历史提交

在实际工程中,有时我们在以往的提交中忘记提交部分代码,当想起来的时候已经提交过很多笔了。于是想要把当前的更改提交到历史 commit 中去。 假设当前我们提交了三次: $ git log commit 0e162000e27ba998d5a92cc04b489e3d3ec0e30d (HEAD -> master) Author: cloxnu <[email protected]> Date: Fri Nov 5 13:46:08 2021 +0800 3rd commit commit f5f470406634bb255e4f19bf62780868afeed32d Author: cloxnu <[email protected]> Date: Fri Nov 5 13:45:21 2021 +0800 2nd commit commit cf8c580d322e459fa1acf4ef1e6d08163aeb1a21 Author: cloxnu <[email protected]> Date: Fri Nov 5 13:41:30 2021 +0800 1st commit 这几次提交就只涉及一个文件 1.txt $ cat 1.txt 11111111 22222222 33333333 其中第一行为第一次提交,第二行为第二次提交,第三行为第三次提交。 此时如果想要将第二次提交增加一个新文件 2.txt,该怎么操作呢? 步骤 首先新建 2.txt $ cat 2....

November 5, 2021 · 3 min · Sidney Liu