Auto - 优雅的iPhone等比例精准适配工具(一. 纯代码)

前言

很多时候我们苦于需要精准的适配各个屏幕尺寸的UI, 通常根据某一种倍数计算的结果并不能满足精准的需求, 随着iPhone设备不同尺寸的增加 这种需求更加迫切, 当然我说的这些都是属于对于产品细节要求苛刻 追求完美的那一部分, 如果你觉得随便适配适配看着还行就够了, 那么现在就可以X掉这个页面了.

问题

各种适配方案中, 针对不同尺寸iPhone适配的最佳方案莫过于等比例适配 (即按照基准屏幕宽度计算出一个比例值, 再按照这个比例值计算出其他宽度屏幕的值), 计算方法大家都会 加减乘除嘛, 但是如何可以优雅的封装 并在开发中更简单的使用就是一个问题了.

解决方案

先说说思路吧.

首先 对于交换某些UI类的某些方法实现 增加等比例计算处理 这类的利用Method Swizzling的实现方案我并不赞同, 例如交换一下UILabel类的setFont方法的实现 增加一些将原有FontSize按照等比例计算的操作等等吧, 可能这类问题仁者见仁智者见智, 但我个人的观点是"对类本身的入侵性太强 极容易出现不可预知的问题" 试想各种工具类都利用Method Swizzling的方式来处理 那么你根本不清楚当你调用一个方法时被其他扩展进行了怎样的处理, 同时遇到Crash时 也很难定位罪魁祸首是谁造成的.

Method Swizzling在我看来只适用于某些快速补救的情景, 如果过分依赖于它 那么整个项目会变得极不稳定, 它在某些情况下对整个项目的健壮性破坏是致命的.

还有一点, UI类无数 每个类会影响到布局效果的属性/方法也是层出不穷, 每一个都要扩展处理会不会累死?

我的思路:

上升一个维度, 既然针对各个UI类进行扩展处理不合适, 那么为什么不直接对数值进行扩展呢?

对各种数值类型进行扩展 增加一个等比例计算转换方法, 开发时即可以灵活的控制数值要不要进行等比例计算, 也可以对于各个UI类不造成任何影响.

设(yi)想(yin)中的使用状态:

label.font = .systemFont(ofSize: 16.auto())

第一步 声明一个自动计算的协议

protocol AutoCalculationable {
    
    /// 自动计算
    ///
    /// - Returns: 结果
    func auto() -> Double
}

针对Double扩展 添加默认实现

extension Double: AutoCalculationable { }

extension AutoCalculationable where Self == Double {
    
    func auto() -> Double {
        guard UIDevice.current.userInterfaceIdiom == .phone else {
            return self
        }
        
        let base = 375.0
        let screenWidth = Double(UIScreen.main.bounds.width)
        let screenHeight = Double(UIScreen.main.bounds.height)
        let width = min(screenWidth, screenHeight)
        return self * (width / base)
    }
}

这里说明一下为什么要用一个协议, 而不是直接对Double进行扩展, 主要是为了方便使用者自定义计算处理逻辑, 不一定所有人的auto()实现都是按照375屏幕宽度计算的, 当不想使用默认实现时, 可以通过扩展Double重写auto()方法的实现来自定义计算逻辑, 如下:

extension Double {
    /// 扩展Double类 重写auto()实现
    func auto() -> Double {
        // ... 自定义计算处理
        return self
    }
}

Double类型的扩展已经加好了, 其他类型怎么办呢?CGFloat, Float, Int, Int8, Int32, Int64.... 所有扩展都写一遍? 不可能的, 这辈子都不可能的.

查看DoubleFloat等浮点类型的声明 我们可以看到所有浮点类型都实现了一个叫做BinaryFloatingPoint的协议, 利用Swift强大的扩展 可以这样做:

extension BinaryFloatingPoint {
    
    func auto() -> Double {
        let temp = Double("\(self)") ?? 0
        return temp.auto()
    }
}

这里再解释一波, 为什么要转成String再转成Double?

转换计算的逻辑其实只要在一个扩展中进行就够了, 其他数值类型的扩展可以转换成这个类型 直接调用该类型的方法就可以了, 这样的好处就是计算逻辑汇总到了一处, 更方便日后调整维护.

Double无疑是最合适的类型 (Swift中浮点字面量的默认类型, 计算时精度比单浮点更准确), 所以你懂得~

那么为什么转成String?

查看Double结构体的初始化方法可以看到并没有给出通过BinaryFloatingPoint协议类型初始化的方法,
但是我们必须要将这任意浮点类型转换Double, 才能使用Double扩展中的auto()进行统一的计算, 苦思冥想, 终于想到了可以利用万能的String来做一个中间者, StringDouble是必然完全阔以的, 最终我将任意浮点类型先转成String, 再将String转成所需的Double, 调用auto()进行计算并返回结果.

Int, Int8, Int32, Int64....类型和Double同理, 它们都实现了一个叫BinaryInteger的协议.

extension BinaryInteger {
    
    func auto() -> Double {
        let temp = Double("\(self)") ?? 0
        return temp.auto()
    }
}

继续继续, 所有数值类型的扩展都搞定了, 现在任何一个数值类型都可以调用auto()方法进行等比例计算了.

调用试试看:

/// Integer
print(1994.auto())
print(Int(1994).auto())
print(Int8(1994).auto())
print(Int16(1994).auto())
print(Int32(1994).auto())
print(Int64(1994).auto())
print(UInt(1994).auto())
print(UInt8(1994).auto())
print(UInt16(1994).auto())
print(UInt32(1994).auto())
print(UInt64(1994).auto())

/// Floating
print(1994.0.auto())
print(Float(1994).auto())
print(Float32(1994).auto())
print(Float64(1994).auto())
print(Float80(1994).auto())
print(Double(1994).auto())
print(CGFloat(1994).auto())

很好, 完美编译.

不过! 所有的auto()方法返回值都是Double, 在Swift的强类型限制中就会出现如下尴尬的情况 各种类型转换:

label.font = .systemFont(ofSize: CGFloat(16.auto()))

别怕, 上泛型:

extension BinaryInteger {
    
    func auto() -> Double {
        let temp = Double("\(self)") ?? 0
        return temp.auto()
    }
    func auto<T: BinaryInteger>() -> T {
        let temp = Double("\(self)") ?? 0
        return temp.auto()
    }
    func auto<T: BinaryFloatingPoint>() -> T {
        let temp = Double("\(self)") ?? 0
        return temp.auto()
    }
}

extension BinaryFloatingPoint {
    
    func auto() -> Double {
        let temp = Double("\(self)") ?? 0
        return temp.auto()
    }
    func auto<T: BinaryInteger>() -> T {
        let temp = Double("\(self)") ?? 0
        return T(temp.auto())
    }
    func auto<T: BinaryFloatingPoint>() -> T {
        let temp = Double("\(self)") ?? 0
        return T(temp.auto())
    }
}

再调用来看看, 完美

label.font = .systemFont(ofSize: 16.auto())

看一下实际使用场景的样子 :

containerView.snp.makeConstraints { (make) in
      make.left.equalTo(10.auto())
      make.top.equalToSuperview()
      make.bottom.equalToSuperview()
      make.right.equalTo(-10.auto())
 }

总结

以上方案可以很好地支持不同设备快捷精准的等比例适配需求, 并且保持良好的扩展性和灵活性, 这里也充分利用了Swift强大的语法特性, 为适配创造了更多的可能.

如果你有更好的想法 欢迎Issues留言讨论, 我是LEE, 下个轮子见.

上一篇 -> 全尺寸精准适配
下一篇 -> 等比例精准适配(二. 可视化)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342