肇鑫的技术博客

业精于勤,荒于嬉

Locale苹果官方文档补遗

我们知道,在Xcode中可以通过指定运行时的语言,来测试相应的翻译。如图,我指定了中文作为在测试时的语言。

测试时指定语言

这个方式,在测试翻译的情况下是没有问题的。

最近我需要在应用中通过WKWebView,根据用户所使用的语言来动态加载网页。为了达到这个目的,我使用了

Locale.current.languageCode

根据苹果的文档,languageCode的含义是

The language code of the locale, or nil if has none.

但是实际使用中,我发现它并不能返回zh

iOS_12.1_Locale_iPhoneX

如图,实际上返回的是en_CN

我就此问题向苹果提交了错误报告。苹果回复说,从macOS 10.13, iOS 11开始,Locale.current.languageCode返回的实际上是Bundle.main.preferredLocalizations.first。这是因为苹果认为这样的用户体验最好。

苹果举例说,如果一个用户对于语言的偏好顺序是[ "zh-CN", "de-CN", "en-CN" ],而某个应用仅支持德语和英语,那么Locale.current.languageCode返回的就是de_CN

按照苹果的建议,如果想要测试中文,那么第一步就是添加中文的翻译。于是我添加了简体中文的翻译。这一下,简体中文的内容正确了。

简体中文

另外,对比一下没有中文翻译时,iOS 10和iOS 11的不同。

Locale 对比

事情还没有完,因为我希望的是用户懂中文时,使用中文;否则使用英文。而现在的方式仅能识别简体中文,当用户使用繁体中文时,还是会显示英文。于是我改了代码,新的代码

private func isChinese() -> Bool {
    let languages = Locale.preferredLanguages
    
    for lang in languages {
        if lang.hasPrefix("zh") {
            return true
        }
    }
    
    return false
}

iOS、watchOS应用用户设置相关问题的答案

最近在写咕唧2,对于iOS应用与配套watchOS应用之间的设置的相关问题有了一些心得。记下来,备查。

问题与答案

1. 创建的Settings.bundle文件放在哪里?

无论iOS还是watchOS的,都放在iOS应用下。

2. iOS应用的设置,为什么watchOS应用不能直接读了?

这是一个历史遗留问题。1代的手表系统,当时还不叫watchOS,上的应用只有一种,叫瞥一眼(glance)。生成机制是所有的一切都在手机上生成,然后传到手表上显示。因此,当然手机应用和手表应用实际上都在手机上运行。因此,直接通过分享组(share group),手表应用就能读取手机应用的设置。

watchOS 2开始,手表应用改在手表上运行了。而手表上的共享组和手机上的共享组之间不自动同步,所以手表和手机之间的设置不能直接读了。

3. iOS应用如何获得watchOS应用的设置?

不能直接读。因为苹果规定,手表应用可以唤醒手机应用,反之则不行。

4. watchOS应用如何获得iOS应用的设置?

通过session的sendMessage方法唤醒手机,然后手机发送设置给手表。

5. 如何设置watchOS应用的设置?

通过手机端的手表应用。这个应用虽然在手机上,但是可以直接读手表应用的设置。并且会写回去。

一张图

iOS与watchOS的设置同步

参考资料

自定义UITableViewCell的选中背景

默认情况下,UITableViewCell的选中效果如图。单独选中的效果给人的感觉不是那么可靠。

但从编程角度看,使用选中状态比较简单。不使用选中,而采用标记替代选中比较麻烦。因为这个对号是属于UITableViewCellaccessoryType属性。它并不是和选中状态同步的。需要单独、小心地处理,稍有不慎,就会出现各种错误。

解决思路

创建一个背景透明的对号标记

我最初的想法是创建一个背景透明的对号标记。然后把它应用在UITableViewCellselectedBackgroundView属性上。

这条路不好走。因为一旦制定了selectedBackgroundView。表格之间的分隔线就没有。解决方案有利用各种方式画分隔线的。比较麻烦。

在我采用默认的表格,并允许多选时,我发现苹果的默认选中,就是没有分割线的,只不过它用了有颜色的背景。这在单选时不明显,一旦有多选,就很明显。

创建一个有背景色的对号标记

创建好的图片如图。

checkmark_background@2x

用1x的语言描述,背景:宽320,高44。对号:宽16,高32,水平居中,右边距16。

难点来了

我们知道,不同格式设备的宽度是不一样的。那么对于320这个iOS设备的最小宽度,这个图片在遇到更宽的设备时是需要拉伸的。

关于如何拉伸图片,我们可以看这篇:UIImage图片拉伸平铺(resizableImage)

看着不难,实际上全是坑。因为思维习惯不是直接的,是间接的。

简述一下拉伸图片的原理

如图,如果有一个图片,被分成1-9共9个部分。那么它的四个角的1、3、7、9图形保持不变。我们通过定义2/上、4/左、8/下、6/右的方式,来确定5的范围,然后选择拉伸5。这个就是定义图片拉伸的原理。

1 2 3
4 5 6
7 8 9

下面应用这个原理,放大上面的带对号的背景图。

checkmark_background 1-9@2x

因为我们需要保证对号的位置不变,所以要把它画在3的位置。这是唯一的限制。根据这个限制我们可以计算出。

  • 上:
    • 上边距 = (背景高 - 对号高) / 2 = (44 - 32) / 2 = 6
    • 上 = 上边距 + 对号高 = 6 + 32 = 38
  • 右 = 对号宽 + 右边距 = 16 + 16 = 32

至于下和左,我们因为要尽量保证5越大越好,就都取1。

最终代码如下:

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "sampleCell", for: indexPath)
    
    // 获得背景图
    let image = #imageLiteral(resourceName: "Checkmark_background")
    // 指定拉伸范围
    let edgeInsets = UIEdgeInsetsMake(38, 1, 1, 32)
    // 获得拉伸图片
    let resizableImage = image.resizableImage(withCapInsets: edgeInsets, resizingMode: .stretch)
    // 生成图片视图,大小与表格项相同
    let imageView = UIImageView(frame: cell.bounds)
    // 指定图片
    imageView.image = resizableImage
    // 指定表格项选中时的背景视图
    cell.selectedBackgroundView = imageView
    // 设定文字
    cell.textLabel?.text = "条件\(indexPath.row)"

    return cell
}

最终效果

如图:

fina