IOS 屏幕适配(三)代码实现篇

2. IOS 屏幕适配代码实现

2.1 布局处理

2.1.1 masonary布局适配实例

  • 在使用masonary自动布局时,可以根据6s的屏幕设计,设置一个比例系数,比如

//以6/6s为准宽度缩小系数

#define kJLXWidthScale [UIScreen mainScreen].bounds.size.height/375.0 

//高度缩小系数

#define kJLXHeightScale [UIScreen mainScreen].bounds.size.height/667.0 
  • 这样在布局的的时候,可以考虑使用上这个系数设置高度
UIButton *createrButton = [[UIButton alloc] init];

[self.view addSubview:createrButton];

UIEdgeInsets padding = UIEdgeInsetsMake(0, 10, 65, 10);

[createrButton setBackgroundImage:[UIImage imageNamed:@"common_button_pink"] forState:UIControlStateNormal];

[createrButton mas_makeConstraints:^(MASConstraintMaker *make){ 

 make.left.equalTo(self.view.mas_left).with.offset(padding.left);

 make.height.equalTo(@(60*kJLXHeightScale));

 make.bottom.equalTo(self.view.mas_bottom).with.offset(-padding.bottom);

 make.right.equalTo(self.view.mas_right).with.offset(-padding.right);

}];
  • 这样在5s小屏手机上面,按钮的高度就会根据比例系数来动态调整大小。

2.1.2 Jimu 1.0 用到的布局适配函数

  • 之前Jimu 1.0中用到的布局转换主要通过下面这个函数来转换实际的宽度或高度:

横屏下,水平方向适配函数

/// 设备横屏下,水平方向适配·
///
/// - Parameters:
///   - iPhone6Scale: iPhone6 水平方向@2x尺寸
///   - iPadScale: 分辨率比例为768*1024的iPad 水平方向@2x尺寸
/// - Returns: 适配后的尺寸
func layoutHorizontal(iPhone6 iPhone6Scale: Float, iPad iPadScale: Float) -> Float {
    
    let iphoneWidth = iPhone6Scale / 2
    let iPadWidth = iPadScale / 2
    
    var newWidth: Float = 0
    
    switch Device.type() {
    case .iPhone4:
        newWidth = iphoneWidth * (480.0 / 667.0)
    case .iPhone5:
        newWidth = iphoneWidth * (568.0 / 667.0)
    case .iPhone6:
        newWidth = iphoneWidth
    case .iPhone6p:
        newWidth = iphoneWidth * (736.0 / 667.0)
    case .iPhoneX:
        newWidth = iphoneWidth * ((812.0 - 78) / 667.0)
    case .iPhoneXR:
        newWidth = iphoneWidth * ((896.0 - 78) / 667.0)
    case .iPad_768_1024:
        newWidth = iPadWidth
    case .iPad_834_1112:
        newWidth = iPadWidth * (1112.0 / 1024.0)
    case .iPad_1024_1366:
        newWidth = iPadWidth * (1366.0 / 1024.0)
    }
    
    return newWidth
}

设备横屏下,垂直方向适配函数

/// 设备横屏下,垂直方向适配
///
/// - Parameters:
///   - iPhone6Scale: iPhone6 垂直方向@2x尺寸
///   - iPadScale: 分辨率比例为768*1024的iPad 垂直方向@2x尺寸
/// - Returns: 适配后的尺寸
func layoutVertical(iPhone6 iPhone6Scale: Float, iPad iPadScale: Float) -> Float {
    
    let iphoneHeight = iPhone6Scale / 2
    let iPadHeight = iPadScale / 2
    
    var newHeight: Float = 0
    
    switch Device.type() {
    case .iPhone4:
        newHeight = iphoneHeight * (320.0 / 375.0)
    case .iPhone5:
        newHeight = iphoneHeight * (320.0 / 375.0)
    case .iPhone6:
        newHeight = iphoneHeight
    case .iPhone6p:
        newHeight = iphoneHeight * (414.0 / 375.0)
    case .iPhoneX:
        newHeight = iphoneHeight * (375.0 / 375.0)
    case .iPhoneXR:
        newHeight = iphoneHeight * (414.0 / 375.0)
    case .iPad_768_1024:
        newHeight = iPadHeight
    case .iPad_834_1112:
        newHeight = iPadHeight * (834.0 / 768.0)
    case .iPad_1024_1366:
        newHeight = iPadHeight * (1024.0 / 768.0)
    }
    
    return newHeight
}

  • 这种适配方式,可以满足横屏下适配各种设备,但是所有布局的代码都需要调用这两个函数,浸入性很强。所以需要优化一下。

2.1.3 布局适配优化

2.1.3.1 增加判断设备类型的extension扩展
  • 先来看一下之前Jimu 1.0 是通过一个自定义枚举来实现的,这样的不好的地方也是浸入性很强,每个调用的地方都需要用这个枚举值。
/// 获取设备型号
enum Device {
    
    case iPhone4            /// 4/4s          320*480  @2x
    case iPhone5            /// 5/5C/5S/SE    320*568  @2x
    case iPhone6            /// 6/6S/7/8      375*667  @2x
    case iPhone6p           /// 6P/6SP/7P/8P  414*736  @3x
    case iPhoneX            /// X             375*812   @3x
    //    case iPhoneXS           /// XS            375*812   @3x (同X)
    case iPhoneXR           /// XR            414*896   @2x (放大模式下为 375*812)
    //    case iPhoneXSMAX        /// XSMAX         414*896   @3x (同XR)
    
    
    case iPad_768_1024      /// iPad(5th generation)/iPad Air/iPad Air2/iPad pro(9.7)  768*1024  @2x
    case iPad_834_1112      /// iPad pro(10.5)  834*1112   @2x
    case iPad_1024_1366     /// iPad pro(12.9)  1024*1366  @2x
    
    
    /// 判断具体设备
    ///
    /// - Returns: 具体设备名
    static func type() -> Device {
        
        switch screenWidth {
        case 480.0:
            return .iPhone4
        case 568.0:
            return .iPhone5
        case 667.0:
            return .iPhone6
        case 736.0:
            return .iPhone6p
        case 812.0:
            return .iPhoneX
        case 896.0:
            return .iPhoneXR
        case 1024.0:
            return .iPad_768_1024
        case 1112.0:
            return .iPad_834_1112
        case 1366.0:
            return .iPad_1024_1366
        default:
            return .iPad_768_1024
        }
    }
    
    /// 判断是否为iPad
    ///
    /// - Returns: true 是, false 否
    static func isIPad() -> Bool  {
        //        print("() = \(self.type())")
        return (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiom.pad)
    }
    
    static func isIPhone5() -> Bool {
        return Device.type() == Device.iPhone5 ? true : false
    }
    
    static var safeAreaInsets: UIEdgeInsets {
        if #available(iOS 11.0, *) {
            return UIApplication.shared.delegate?.window??.safeAreaInsets ?? .zero
        }
        return .zero
    }
    
    static var safeScreenWidth: CGFloat {
        return UIScreen.main.bounds.width-safeAreaInsets.left-safeAreaInsets.right
    }
    
    static var safeScreenHeight: CGFloat {
        return UIScreen.main.bounds.height-safeAreaInsets.top-safeAreaInsets.bottom
    }
    
}
  • 将上面设备类型判断代码优化为UIDevice的一个类扩展
extension UIDevice {
    
    func Version()->String{
        
        let appVersion: String = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as! String
        return appVersion
    }
    
    
    @objc public class func isiPhoneX() -> Bool {
        if (UIScreen.main.currentMode?.size.equalTo(CGSize.init(width: 1125, height: 2436)))! {
            return true
        }
        return false
    }
    
    public class func isiPhone6PlusBigMode() -> Bool {
        if (UIScreen.main.currentMode?.size.equalTo(CGSize.init(width: 1125, height: 2001)))! {
            return true
        }
        return false
    }
    
    public class func isiPhone6Plus() -> Bool {
        if (UIScreen.main.currentMode?.size.equalTo(CGSize.init(width:1242, height: 2208)))! {
            return true
        }
        return false
    }
    
    public class func isiPhone6BigMode() -> Bool{
        if (UIScreen.main.currentMode?.size.equalTo(CGSize.init(width: 320, height: 568)))! {
            return true
        }
        return false
    }
    
    public class func isiPhone6() -> Bool {
        if (UIScreen.main.currentMode?.size.equalTo(CGSize.init(width:750, height: 1334)))! {
            return true
        }
        return false
    }
    
    public class func isiPhone5() -> Bool {
        if (UIScreen.main.currentMode?.size.equalTo(CGSize.init(width: 640, height: 1136)))! {
            return true
        }
        return false
    }
    
    public class func isiOS11() -> Bool {
        if #available(iOS 11.0, *) {
            return true
        } else {
            return false
        }
    }
    
    public class func isiOS10() -> Bool {
        if #available(iOS 10.0, *) {
            return true
        } else {
            return false
        }
    }
    
    public class func isiOS9() -> Bool {
        if #available(iOS 9.0, *) {
            return true
        } else {
            return false
        }
    }
    
    public class func isiOS8() -> Bool {
        if #available(iOS 8.0, *) {
            return true
        } else {
            return false
        }
    }
    
    public class func isAiPad() -> Bool {
        if UIDevice.current.userInterfaceIdiom == UIUserInterfaceIdiom.pad {
            return true
        }
        return false
    }
}

  • 然后为了简化调用,可以定义一个全局变量
// MARK: - 判断 机型
let isiPhone5 = UIDevice.isiPhone5()
let isiPhone6 = UIDevice.isiPhone6()
let isiPhone6BigModel = UIDevice.isiPhone6BigMode()
let isiPhone6Plus = UIDevice.isiPhone6Plus()
let isiPhone6PlusBigMode = UIDevice.isiPhone6PlusBigMode()
let isiPhoneX = UIDevice.isiPhoneX()
let isIpad = UIDevice.isAiPad()

// MARK: - 系统类型
let kisiOS11 = UIDevice.isiOS11()
let kisiOS10 = UIDevice.isiOS10()
let kisiOS9 = UIDevice.isiOS9()
let kisiOS8 = UIDevice.isiOS8()
  • 定义全局变量简化屏幕宽度,高度计算
let screenWidth = max(UIScreen.main.bounds.height, UIScreen.main.bounds.width)
let screenHeight = min(UIScreen.main.bounds.height, UIScreen.main.bounds.width)
let screenBounds = UIScreen.main.bounds
2.1.3.2 增加 NSInteger 类扩展
extension NSInteger {
    /// iphone 5 上的大小
    /// ? 《*注意运算顺序 -60.i5(-30) 等价于 -(60.i5(-30)) 结果为 -(-30) 或者 -60》
    ///
    /// - Parameter size: iphone 5 上的大小
    /// - Returns: isiPhone5 ? size : CGFloat(self)
    func i5(_ size: CGFloat) -> CGFloat {
        return isiPhone5 ? size : CGFloat(self)
    }
    
    /// iphone 6 放大模式上的大小
    /// ? 《*注意运算顺序 -60.i6BigModel(-30) 等价于 -(60.i6BigModel(-30)) 结果为 -(-30) 或者 -60》
    ///
    /// - Parameter size: iphone 6 放大模式 上的大小
    /// - Returns: isiPhone6BigModel ? size : CGFloat(self)
    func i6BigModel(_ size: CGFloat) -> CGFloat {
        return isiPhone6BigModel ? size : CGFloat(self)
    }
    
    /// iphone 6p 放大模式上的大小
    /// ? 《*注意运算顺序 -60.i6PBigModel(-30) 等价于 -(60.i6PBigModel(-30)) 结果为 -(-30) 或者 -60》
    ///
    /// - Parameter size: iphone 6p 放大模式 上的大小
    /// - Returns: isiPhone6PlusBigMode ? size  : CGFloat(self)
    func i6PBigModel(_ size: CGFloat) -> CGFloat {
        return isiPhone6PlusBigMode ? size : CGFloat(self)
    }
    
    /// iphone x 上的大小
    /// ? 《*注意运算顺序 -60.ix(-30) 等价于 -(60.ix(-30)) 结果为 -(-30) 或者 -60》
    ///
    /// - Parameter size: iphone x 上的大小
    /// - Returns: isiPhoneX ? size / 2.0 : CGFloat(self)
    func ix(_ size: CGFloat) -> CGFloat {
        return isiPhoneX ? size : CGFloat(self)
    }
    
    /// ipad
    /// ? 《*注意运算顺序 -60.ipad(-30) 等价于 -(60.ipad(-30)) 结果为 -(-30) 或者 -60》
    ///
    /// - Parameter size: ipad 上的大小
    /// - Returns: isIpad ? size : CGFloat(self)
    func ipad(_ size: CGFloat) -> CGFloat {
        return isIpad ? size : CGFloat(self)
    }
    
    /// 比例缩放 width
    ///
    /// - Parameter size: origin width
    /// - Returns: 比例缩放后的 width 没有除以2.0
    func scaleW() -> CGFloat {
        return (screenWidth / 375 * CGFloat(self))
    }
    /// 比例缩放 height result没有除以2.0
    ///
    /// - Parameter size: origin height
    /// - Returns: 比例缩放后的 height 没有除以2.0
    func scaleH() -> CGFloat {
        return (screenHeight / 667 * CGFloat(self))
    }
}

2.1.3.3 增加 CGFloat 类扩展
extension CGFloat {
    
    /// iphone 5 上的大小
    /// ? 《*注意运算顺序 -60.i5(-30) 等价于 -(60.i5(-30)) 结果为 -(-30) 或者 -60》
    ///
    /// - Parameter size: iphone 5 上的大小
    /// - Returns: isiPhone5 ? size : self
    func i5(_ size: CGFloat) -> CGFloat {
        return isiPhone5 ? size : self
    }
    
    /// iphone 6 放大模式上的大小
    /// ? 《*注意运算顺序 -60.i6BigModel(-30) 等价于 -(60.i6BigModel(-30)) 结果为 -(-30) 或者 -60》
    ///
    /// - Parameter size: iphone 6 放大模式 上的大小
    /// - Returns: isiPhone6BigModel ?  : self
    func i6BigModel(_ size: CGFloat) -> CGFloat {
        return isiPhone6BigModel ? size : self
    }
    
    /// iphone 6p 放大模式上的大小
    /// ? 《*注意运算顺序 -60.i6PBigModel(-30) 等价于 -(60.i6PBigModel(-30)) 结果为 -(-30) 或者 -60》
    ///
    /// - Parameter size: iphone 6p 放大模式 上的大小
    /// - Returns: isiPhone6PlusBigMode ? size : self
    func i6PBigModel(_ size: CGFloat) -> CGFloat {
        return isiPhone6PlusBigMode ? size : self
    }
    
    /// iphone x上的大小
    /// ? 《*注意运算顺序 -60.ix(-30) 等价于 -(60.ix(-30)) 结果为 -(-30) 或者 -60》
    ///
    /// - Parameter size: iphone x 上的大小
    /// - Returns: isiPhoneX ? size  : self
    func ix(_ size: CGFloat) -> CGFloat {
        return isiPhoneX ? size : self
    }
    
    /// ipad 上的大小
    /// ? 《*注意运算顺序 -60.ipad(-30) 等价于 -(60.ipad(-30)) 结果为 -(-30) 或者 -60》
    ///
    /// - Parameter size: ipad 上的大小
    /// - Returns: isIpad ? size : self
    func ipad(_ size: CGFloat) -> CGFloat {
        return isIpad ? size : self
    }
    
    
    /// 比例缩放 width
    ///
    /// - Parameter size: origin width
    /// - Returns: 比例缩放后的 width 没有除以2.0
    func scaleW() -> CGFloat {
        return (screenWidth / 375 * self)
    }
    /// 比例缩放 height
    ///
    /// - Parameter size: origin height
    /// - Returns: 比例缩放后的 height 没有除以2.0
    func scaleH() -> CGFloat {
        return (screenHeight / 667 * self)
    }
}
2.1.3.4 增加 Bool 类扩展
extension Bool {
    /// iphone 5 上的大小
    ///
    /// - Parameter size: iphone 5 上的大小
    /// - Returns: isiPhone5 ? size : self
    func i5(_ size: Bool) -> Bool {
        return isiPhone5 ? size : self
    }
    
    /// iphone 6 放大模式上的大小
    ///
    /// - Parameter size: iphone 6 放大模式 上的大小
    /// - Returns: isiPhone6BigModel ? size : self
    func i6BigModel(_ size: Bool) -> Bool {
        return isiPhone6BigModel ? size : self
    }
    
    /// iphone 6p 放大模式上的大小
    ///
    /// - Parameter size: iphone 6p 放大模式 上的大小
    /// - Returns: isiPhone6PlusBigMode ? size  : self
    func i6PBigModel(_ size: Bool) -> Bool {
        return isiPhone6PlusBigMode ? size : self
    }
    
    /// iphone x 上的大小
    ///
    /// - Parameter size: iphone x 上的大小
    /// - Returns: isiPhoneX ? size / 2.0 : self
    func ix(_ size: Bool) -> Bool {
        return isiPhoneX ? size : self
    }
    
    /// ipad
    ///
    /// - Parameter size: ipad 上的大小
    /// - Returns: isIpad ? size : self
    func ipad(_ size: Bool) -> Bool {
        return isIpad ? size : self
    }
}

2.2 图片适配处理

  • 在项目中经常有这样的需求:如下图,截取一部分拉伸,其他不变


实现代码如下:

UIImage *img = [UIImage imageNamed:@"popup"];

img = [img resizableImageWithCapInsets:UIEdgeInsetsMake(0, 13, 0, 55) resizingMode:UIImageResizingModeStretch];

self.resizableImgView.image = img;

swift 代码如下

/// 从中间拉伸图片
    ///
    /// - Parameter image: 拉伸之前原始图
    /// - Returns: 拉伸后图片
    static func stretchFromCenter(image: UIImage?) -> UIImage? {
        guard let oriImage = image else {
            return nil
        }
        let result = oriImage.resizableImage(withCapInsets: UIEdgeInsetsMake(oriImage.size.height/2, oriImage.size.width/2, oriImage.size.height/2, oriImage.size.width/2), resizingMode: .stretch)
        return result
    }
  • 平铺图片:即一张小图可以平铺为多张小图
    平铺图片
    实现代码如下:
UIImage *img = [UIImage imageNamed:@"about"];

img = [img resizableImageWithCapInsets:UIEdgeInsetsMake(0, 11.5, 0, 11) resizingMode:UIImageResizingModeTile];

self.resizableImgView.image = img;
  • 通过纯颜色创建图片
/// 通过纯色创建图片
    ///
    /// - Parameter color: 颜色
    /// - Returns: 通过纯颜色创建的图片
    static func createImage(with color: UIColor) -> UIImage {
        let rect = CGRect(x: 0.0, y: 0.0, width: 1.0, height: 1.0)
        UIGraphicsBeginImageContext(rect.size)
        let ctx = UIGraphicsGetCurrentContext()
        guard let context = ctx else { return UIImage() }
        context.setFillColor(color.cgColor)
        context.fill(rect)
        let theImage: UIImage? = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return theImage ?? UIImage()
    }

2.3 文字适配处理

2.3.1 根据字符串计算宽度,高度

  • 自动适配原则上UILabel都是不设置高度的,根据文字内容自动适配高度。这个时候我们经常需要用到根据文字String 字符串来计算整个字符串的宽度和高度。

jimu 1.0 用到的计算方法如下:

extension String {

func calculateSize(_ size: CGSize, font: UIFont) -> CGSize {
        let paragraphStyle = NSMutableParagraphStyle()
        //        paragraphStyle.lineSpacing = 7
        paragraphStyle.lineBreakMode = .byCharWrapping
        let attributes = [NSAttributedStringKey.font:font, NSAttributedStringKey.paragraphStyle:paragraphStyle.copy()]
        let expectedLabelSize = (self as NSString).boundingRect(with: size, options: [.usesLineFragmentOrigin, .usesFontLeading], attributes: attributes, context: nil).size
        return expectedLabelSize
    }
    

    func getWidth(font: UIFont) -> CGFloat {
        let attrs = [NSAttributedStringKey.font : font]
       return (self as NSString).boundingRect(with: CGSize.zero, options: .usesLineFragmentOrigin, attributes: attrs, context: nil).size.width
    }
}

// 计算文字高度或者宽度与weight参数无关
extension String {
    func ga_widthForComment(fontSize: CGFloat, height: CGFloat = 15) -> CGFloat {
        let font = UIFont.systemFont(ofSize: fontSize)
        let rect = NSString(string: self).boundingRect(with: CGSize(width: CGFloat(MAXFLOAT), height: height), options: .usesLineFragmentOrigin, attributes: [NSAttributedStringKey.font: font], context: nil)
        return ceil(rect.width)
    }
    
    func ga_heightForComment(fontSize: CGFloat, width: CGFloat) -> CGFloat {
        let font = UIFont.systemFont(ofSize: fontSize)
        let rect = NSString(string: self).boundingRect(with: CGSize(width: width, height: CGFloat(MAXFLOAT)), options: .usesLineFragmentOrigin, attributes: [NSAttributedStringKey.font: font], context: nil)
        return ceil(rect.height)
    }
    
    func ga_heightForComment(fontSize: CGFloat, width: CGFloat, maxHeight: CGFloat) -> CGFloat {
        let font = UIFont.systemFont(ofSize: fontSize)
        let rect = NSString(string: self).boundingRect(with: CGSize(width: width, height: CGFloat(MAXFLOAT)), options: .usesLineFragmentOrigin, attributes: [NSAttributedStringKey.font: font], context: nil)
        return ceil(rect.height)>maxHeight ? maxHeight : ceil(rect.height)
    }
}

2.3.2 UIColor 转换

extension UIColor {
    
    
    /// RGB颜色
    ///
    /// - Parameters:
    ///   - red: R
    ///   - green: G
    ///   - blue: B
    ///   - alpha: A
    convenience init(red:Int, green:Int, blue:Int, alpha:CGFloat = 1.0) {
        self.init(red: CGFloat(red)/255.0, green: CGFloat(green)/255.0, blue: CGFloat(blue)/255.0, alpha: alpha)
    }
    
    
    /// 16进制颜色
    ///
    /// - Parameters:
    ///   - rgb: RGB Int值
    ///   - alpha: 透明度
    convenience init(hex rgb:Int, alpha:CGFloat = 1.0) {
        self.init(red: (rgb >> 16) & 0xFF, green: (rgb >> 8) & 0xFF, blue: rgb & 0xFF, alpha: alpha)
    }

    
    /// 随机颜色
    ///
    /// - Parameter randomAlpha: 是否随机透明度,默认false
    /// - Returns: 随机颜色
    public static func random(randomAlpha: Bool = false) -> UIColor {
        let randomRed = CGFloat(Float(arc4random()) / 0xFFFFFFFF)
        let randomGreen = CGFloat(Float(arc4random()) / 0xFFFFFFFF)
        let randomBlue = CGFloat(Float(arc4random()) / 0xFFFFFFFF)
        let alpha = randomAlpha ? CGFloat(Float(arc4random()) / 0xFFFFFFFF) : 1.0
        return UIColor(red: randomRed, green: randomGreen, blue: randomBlue, alpha: alpha)
    }
    
    /// Hex String -> UIColor
    convenience init(hexString: String, alpha: CGFloat = 1.0) {
        let hexString = hexString.trimmingCharacters(in: .whitespacesAndNewlines)
        let scanner = Scanner(string: hexString)
        
        if hexString.hasPrefix("#") {
            scanner.scanLocation = 1
        }
        
        var color: UInt32 = 0
        scanner.scanHexInt32(&color)
        
        let mask = 0x000000FF
        let r = Int(color >> 16) & mask
        let g = Int(color >> 8) & mask
        let b = Int(color) & mask
        
        let red   = CGFloat(r) / 255.0
        let green = CGFloat(g) / 255.0
        let blue  = CGFloat(b) / 255.0
        
        self.init(red: red, green: green, blue: blue, alpha: alpha)
    }
}

2.3.3

2.4 特殊控件适配处理

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值