全身心写几个月 swift 了, 第一次不依赖谷歌完全用自己的思路解决的一个小问题, 和大家分享一下, 希望大家能说些难听的话.🙇♀️
聊天软件几乎都有输入框, 配合键盘的唤出, 用于区分编辑状态, 十分符合人类直觉.
微信等聊天软件的通用逻辑是:
可是. 等待键盘的过程, 是一个动画, 用户只能看, 无法干预. 有那么半秒钟, 我的手机不听我的了, 自己玩了.
简单来说: “不够丝滑”.
「推敲」是允许你这样做的:
解耦出来看这并不难, 但「推敲」的一个核心功能是精准记录单个话语的编辑时长, 如何在暴力驾驶中依然完成精确的编辑计时呢?
丝滑的极致是一切不更新到视图上的进程都不上主线程. 丝滑的代价是一切视图上的进程随时能被干预
最初的实现采用的 Combine 框架, 但我的嗅觉告诉我 Combine 迟早会被弃用, 借这个机会替换掉,只 import swiftUI 和 os.log.
看见了钉子, 选好锤子, 下面就是砸.
思路:
划拉一块内存, 登记为 actor, 叫计时工. 予左手右手各一把章. 用户唤起键盘时左手戳一下, 键盘时他右手戳一下, 用户提交话语的时候双手一拍, 返回总时长.
实现:
计时工入职培训:
**actor** TimingManager {
**private** **var** timeStack:[(Date,Date)] = []
**private** **var** aStart: Date?
**func** onFocus() {
aStart = Date()
logger.debug("onFocus()")
}
**func** endFocus() {
**if** **let** left = aStart {
timeStack.append((left,Date()))
aStart = **nil**
logger.debug("endFocus!")
}
}
**func** handClose() -> Int {
**guard** !timeStack.isEmpty **else** {**return** 0}
logger.debug("\(**self**.timeStack.debugDescription)")
**var** sec: Double = 0
**for** hand **in** timeStack {
sec += hand.1.timeIntervalSince(hand.0)
}
clearHand()
**return** Int(sec)
}
**func** clearHand() {
timeStack.removeAll()
}
}
计时工的日常工作, 追踪键盘是否抬起.
.onChange(of: focuing) { focuing **in**
**switch** focuing {
**case** **true**:
Task {
**await** viewModel.timeAcotr.onFocus()
typerOffset = -340
}
**case** **false**:
Task {
**await** viewModel.timeAcotr.endFocus()
typerOffset = 0
}
}
}
计时工的汇报工作:
**func** submitted() {
**if** aword.text == "" {
Task { **await** timeAcotr.clearHand() }
aword = Aword()
} **else** {
Task {@MainActor **in**
**await** timeAcotr.endFocus()
aword.secondSpent += **await** timeAcotr.handClose()
withAnimation {
wordsPool.append(aword)
}
aword = Aword()
}
}
}
我觉得作为一个产品「推敲」已经挺棒了的, 至少已经改善我生活了.
但我甚至不敢在这里 po 个链接.
😮💨
这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。
V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。
V2EX is a community of developers, designers and creative people.