@[TOC](IOS screen Adaptation (A) Theory chapter)

1. Basic concepts of IOS screen adaptation

1.1 Size and resolution of IOS devices

1.1.1 Resolution related concepts

  • Points:

Is an abstract unit introduced in iOS development called a point. In the development process, all coordinate system-based rendering takes Point as the unit. In the age of iPhone 2G,3G and 3GS, point corresponds to pixels on the screen exactly one by one, namely 320 * 480 (points) and 320 * 480 (Pixels).

  • Rendered Pixels (Rendered Pixels) :

Rendered Pixels: Any Rendered point will eventually be Rendered in Pixels, a process known as rasterization. A pixel-based coordinate system is obtained by multiplying a point based coordinate system by a scale factor. A higher scale factor gives more detail. The current scale factors are 1x, 2x, 3x

  • Physical Pixels:

Physical Pixels: Physical Pixels, the actual Pixels on the screen of a device

  • Physical Device screen length:

Physical Device: Indicates the Physical length of the Device screen, in inches. For example, the iPhone 4 screen is 3.5 inches, the iPhone 5 is 4 inches, and the iPhone 6 is 4.7 inches. The number here refers to the diagonal physical length of the screen. It’s actually the Physical Pixels (not the Rendered Pixels) that will be Rendered on the screen, and the screen will have a PPI(Pronounced GPIS-inch), which tells you how many Pixels per inch will be Rendered.

1.1.2 Resolution of IOS devices

  • For more information about IOS device Resolutions, see The Ultimate Guide To iPhone Resolutions
models Screen Width and Height (Point) Render pixels Physical pixels Screen diagonal length (in.) Screen mode
iPhone 2G,3G,3GS 320 * 480 320 * 480 320 * 480 3.5 (163 ppi) 1x
iPhone 4, 4s 320 * 480 640 * 960 640 * 960 3.5 (326 ppi) 2x
iPhone 5, 5s 320 * 568 640 * 1136 640 * 1136 4 (326PPI) 2x
iPhone 6, 6s, 7 375 * 667 750 * 1334 750 * 1334 4.7 (326 ppi) 2x
iPhone 6 Plus, 6s Plus, 7 Plus 414 * 736 1242 * 2208 1080 * 1920 5.5 (401 ppi) 3x
  • IPhone Device size
models Screen Width and Height (Point) The proportion Pixel density (PPI) The screen size Model code Release date
iPhone 2g 480 x 320 3:2 163ppi 3.5 IPhone1, 1 2008.01
iPhone 3g 480 x 320 3:2 163ppi 3.5 IPhone1, 2 2008.06
iPhone 3gs 480 x 320 3:2 163ppi 3.5 IPhone2, 1 2009.06
iPhone 4 960 x 640 3:2 163ppi 3.5 IPhone3,1, iPhone3,2, iPhone3,3 2010.06
iPhone 4s 960 x 640 3:2 326ppi 3.5 The iphone 4, 1 2011.10
iPhone 5 1136 x 640 16:9 326ppi 4.0 The iphone 5, 1, the iphone 5, 2 2012.09
iPhone 5c 1136 x 640 16:9 326ppi 4.0 The iphone 3, iphone 4 2013.09
iPhone 5s 1136 x 640 16:9 326ppi 4.0 IPhone6, 1, iPhone6, 2 2013.09
iPhone 6 1334 x 750 16:9 401ppi 4.7 IPhone7, 2 2014.09
iPhone 6 plus 1920 x 1080 16:9 401ppi 5.5 IPhone7, 1 2014.09
iPhone 6s 1334 x 750 16:9 401ppi 4.7 IPhone8, 2 2015.09
iPhone 6s plus 1920 x 1080 16:9 401ppi 5.5 IPhone8, 1 2015.09
iPhone 5 SE 1136 x 640 16:9 401ppi 4.0 IPhone8, 4 2016.03
iPhone 7 1334 x 750 16:9 401ppi 4.7 IPhone9, 1, iPhone9, 3 2016.09
iPhone 7 plus 1920 x 1080 16:9 401ppi 5.5 IPhone9, 2, iPhone9, 4 2016.09
iPhone 8 1334 x 750 16:9 401ppi 4.7 IPhone10, 1, iPhone10, 4 2017.09
iPhone 8 plus 1920 x 1080 16:9 401ppi 5.5 IPhone10, 2, iPhone10, 5 2017.09
iPhone X 2436 x 1125 and 458ppi 5.8 IPhone10, 3, iPhone10, 6 2017.09
iPhone XS 2436 x 1125 and 458ppi 5.8 IPhone11, 2 2018.09
iPhone XS Max 2688 x 1242 and 458ppi 6.5 IPhone11, 4, iPhone11, 6 2018.09
iPhone XR 1792 x 828 19.5:9 326ppi 6.1 IPhone11, 8 2018.09
  • Its size
models Screen Width and Height (Point) Screen mode (Scale) Physical pixels The proportion Pixel density (PPI) The screen size Model code Release date
iPad 1024 x 768 @1x 1024 x 768 4:3 163ppi 9.7 IPad1, 1 2010.01
iPad 2 1024 x 768 @1x 1024 x 768 4:3 163ppi 9.7 IPad2,1, iPad2,2, iPad2,3, iPad2,4 2011.03
iPad 3(New) 1024 x 768 @2x 2048 x 1536 4:3 264ppi 9.7 IPad3,1, iPad3,2, iPad3,3 2012.03
iPad 4 1024 x 768 @2x 2048 x 1536 4:3 264ppi 9.7 IPad3,4, iPad3,5, iPad3,6 2012.10
iPad 5 1024 x 768 @2x 2048 x 1536 4:3 264ppi 9.7 IPad6, 11, iPad6, 12 2017.03
  • The size of the Air
models Screen Width and Height (Point) Screen mode (Scale) Physical pixels The proportion Pixel density (PPI) The screen size Model code Release date
iPad Air 1024 x 768 @2x 2048 x 1536 4:3 264ppi 9.7 IPad4,1, iPad4,2, iPad4,3 2013.10
iPad Air 2 1024 x 768 @2x 2048 x 1536 4:3 264ppi 9.7 Lofty, 3, lofty, 4 2014.10
  • The size of Pro
models Screen Width and Height (Point) Screen mode (Scale) Physical pixels The proportion Pixel density (PPI) The screen size Model code Release date
The 12.9 inch Pro 1366 x 1024 @2x 2732 x 2048 4:3 264ppi 12.9 IPad6, 7, iPad6, 8 2015.09
The 9.7 inch Pro 1024 x 768 @2x 2048 x 1536 4:3 264ppi 9.7 IPad6, 3, iPad6, 4 2016.03
The 12.9 inch Pro 2 1366 x 1024 @2x 2732 x 2048 4:3 264ppi 12.9 IPad7, 1, iPad7, 2 2017
The Pro 10.5 1112 x 834 @2x 2224 x 1668 4:3 264ppi 10.5 IPad7, 3, iPad7, 4
  • The Mini size
models Screen Width and Height (Point) Screen mode (Scale) Physical pixels The proportion Pixel density (PPI) The screen size Model code Release date
iPad mini 1024 x 768 @1X 1024 x 768 4:3 163 7.9 IPad2,5, iPad2,6, iPad2,7 2012.10
iPad mini 2 1024 x 768 @2X 2048 x 1536 4:3 326 7.9 IPad4,5, iPad4,6, iPad4,7 2013.10
iPad mini 3 1024 x 768 @2X 2048 x 1536 4:3 326 7.9 IPad4,7, iPad4,8, iPad4,9 2014.10
iPad mini 4 1024 x 768 @2X 2048 x 1536 4:3 326 7.9 Lofty, 1, lofty, 2 2015.09
  • The iPod Touch size
models Screen Width and Height (Point) Screen mode (Scale) Physical pixels The proportion Pixel density (PPI) The screen size Model code Release date
iTouch 480 * 320 @1X 480 * 320 3:2 163ppi 3.5 IPod1, 1 2007.09
iTouch 2 480 * 320 @1X 480 * 320 3:2 163ppi 3.5 IPod2, 1 2008.09
iTouch 3 480 * 320 @1X 480 * 320 3:2 163ppi 3.5 IPod3, 1 2009.09
iTouch 4 480 * 320 @2X 960 * 640 3:2 326ppi 3.5 IPod4, 1 2010.09
iTouch 5 568 * 320 @2X 1136 * 640 16:9 326ppi 4.0 IPod5, 1 2012.09
iTouch 6 568 * 320 @2X 1136 * 640 16:9 326ppi 4.0 IPod7, 1 2015.07
  • 1x, 2x, 3x

Rendered in screen mode, a point on the screen will be Rendered 2 x 2 = 4 Pixels, and Rendered 3 x 2 = 4 Pixels, Rendered 3 x 2 = 4 Pixels, Rendered 3 x 2 = 4 Pixels, Rendered 3 x 2 = 4 Pixels There will be an area rendering of 3 * 3 = 9 pixels.

In iOS development, all control coordinates and control size are in points. If I need to show a 20 × 20 (point) image on the screen, how should the designer give me the image? The concept of screen mode is used here. If the screen is 2X, you need to provide images of 40 x 40 (pixel), and if the screen is 3X, you need to provide images of 60 x 60, and the naming of the images should follow the following guidelines: Standard:<device_modifier>.<filename_extension> High resolution:@2x<device_modifier>.<filename_extension> High HD resolution:@3x<device_modifier>.<filename_extension>

ImageName: name of the image, named according to the scenario device_modifier: Optional. The modifier can be ~ iPad or ~ iPhone. Filename_extension is required when you need to specify a set of images for iPad and iPhone. Image name extension. PNG image is used in iOS

Such as: Myimage.png – 1x Automatic loading image version [email protected] – 2x Automatic loading image version [email protected] – 3x automatic loading image version MyImage@2x~iphone.png – 2x iPhone and iPod Touch display auto loading image version MyImage@3x~ iphon.png-3x iPhone and iPod display auto loading image version

  • Devices with 2x screen will automatically load image resources named [email protected], and devices with 3X screen will automatically load images named [email protected]. Now there are almost no devices with 1x screen, so you don’t need to provide images with this resolution.

1.2 Multi-screen adaptation between design and development

  • Now APP design and development must consider the adaptation of large, medium and small screens. So how to deliver a set of design draft to solve the problem of adapting large, medium and small screen? What are the patterns of collaboration between design and development?

One basic idea is:

  1. Selecting a size as a design and development benchmark;
  2. Define a set of adaptation rules, automatic adaptation of the remaining two sizes;
  3. Special fit effect gives the design effect.

For more details, please refer to this article: Design scheme of Mobile Taobao

  • Refer to the design scheme of mobile Taobao as follows:
  1. In the first step, the visual design stage, the designer made the design draft with a width of 750px (iPhone 6), and all design elements except the picture were made by vector path. After the final design, annotate the 750px design draft and output the annotation map. At the same time, zoom in 1.5 times to create a 1125px design and cut the image into a 1125px draft.
  2. Step 2: Output two deliverables to the development engineer: one is the @3x slice resource used by the program, and the other is a 750px design annotation.
  3. Step 3: Developers get 750px annotations and @3x cutout resources to complete iPhone 6 (375pt) interface development. At this stage, the interface cannot be developed in a fixed width way. Instead, an auto layout is used to facilitate subsequent adaptation to other sizes.
  4. Step 4: Adaptation debugging phase. Based on the interface effect of iPhone 6, debug the interface effect of iPhone 6 Plus (414PT) and iPhone 5S and below (320pt). Thus complete the large, medium and small screen adaptation.
  • Why choose the iPhone 6 as the base size?

When facing the three kinds of screen need to adapt, it is easy to think of a good screen first, and then to adapt the remaining two kinds of screen. The first decision is what screen size to use as a baseline for design and development. We chose the mid-size iPhone 6 (750px/375pt) as the benchmark for several reasons:

  1. The interface adjusts least when it ADAPTS from the middle size up and down. The design effect at 375pt will not deviate too much from 414pt and 320pt. Let’s say you have an elegant design based on 414pt, but by 320pt the ratio between elements may not be the same, for example the visual ratio between images and text may be out of balance.
  2. The iPhone 6 Plus comes in two display modes, with a standard resolution of 1242×2208 and a magnify resolution of 1125×2001 (1.5 times that of the iPhone 6). It can be seen that there is a 1.5x ratio between the iPhone 6 and iPhone 6 Plus resolution in the official system. In many cases these two sizes can be directly matched by 1.5 times.
  3. The bizarre 1242×2208 number is a resolution that Apple has been reluctant to advertise publicly, making it difficult to memorize and calculate grids. Although 640×1136 is a widely used resolution, it is obviously inappropriate to take small size as the design benchmark in the era of large screen. Designers will stay in the perspective of small screen to do design. So the iPhone6’s 750×1334 is the best fit for the base size

1.3 Adaptation specifications during development

  • Adaptation rules: text flow, control elasticity, picture scaling.

Control elasticity refers to that the height of navigation, cell and bar in the vertical direction remains unchanged during the adaptation process. When the horizontal width changes, the element spacing or element right alignment can be adjusted to achieve self-adaptation. The larger the screen, the more content you can display vertically, taking advantage of the larger screen.

2. IOS screen adaptation code implementation

2.1 Layout Processing

2.1.1 Masonary Layout Adaptation Example

  • When using masonary auto layout, you can set a scale factor based on the screen design of the 6S, for example

// Width reduction factor based on 6/6s

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

// Height reduction coefficient

#define kJLXHeightScale [UIScreen mainScreen].bounds.size.height/667.0 
Copy the code
  • In this layout, you can consider using this coefficient to set the height
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);

}];
Copy the code
  • In this way, the height of the buttons on the 5s small-screen phone will adjust dynamically according to the scale factor.

2.1.2 Layout adaptation functions used by Jimu 1.0

  • The layout conversion previously used in Jimu 1.0 mainly uses the following function to convert the actual width or height:

In landscape, the horizontal adaptation function

// When the device is in landscape mode, the horizontal direction is suitable
///
/// - Parameters:
/// - iPhone6Scale: iPhone6 horizontal @2x size
/// - iPadScale: horizontal @2x size of iPad with resolution ratio of 768*1024
/// - Returns: specifies the size after adaptation
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
}
Copy the code

Vertical adaptation function for device landscape

/// The device is vertically adapted in landscape mode
///
/// - Parameters:
/// - iPhone6Scale: iPhone6 vertical @2x size
/// - iPadScale: 768*1024 iPad vertical @2x size
/// - Returns: specifies the size after adaptation
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
}

Copy the code
  • This adaptation mode can meet the requirements of landscape adaptation of various devices, but all the layout code needs to call these two functions, immersion is very strong. So we need to optimize it.

2.1.3 Layout optimization

2.1.3.1 Adding the Extension extension for Determining the device type

  • The downside of Jimu 1.0 being implemented with a custom enumeration is that it is very immersive, requiring this enumeration value for every invocation.
/// Obtain the device model
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
    case iPhoneXR           XR 414*896 @2x (in zoom mode, 375*812)
    // case iPhone Xmax // XSMAX 414* 896@3x (same as 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
    
    
    /// determine the specific device
    ///
    /// - Returns: specifies the device name
    static func type(a) -> 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
        }
    }
    
    // check whether it is iPad
    ///
    /// - Returns: true Yes, false No
    static func isIPad(a) -> Bool  {
        // print("() = \(self.type())")
        return (UI_USER_INTERFACE_IDIOM() = =UIUserInterfaceIdiom.pad)
    }
    
    static func isIPhone5(a) -> 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
    }
    
}
Copy the code
  • Optimize the device type determination code above as a class extension of UIDevice
extension UIDevice {
    
    func Version(a)->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}}Copy the code
  • You can then define a global variable to simplify the call
// MARK: - Determine the model
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: - System type
let kisiOS11 = UIDevice.isiOS11()
let kisiOS10 = UIDevice.isiOS10()
let kisiOS9 = UIDevice.isiOS9()
let kisiOS8 = UIDevice.isiOS8()
Copy the code
  • Define global variables to simplify screen width and height calculations
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
Copy the code

2.1.3.2 Adding an NSInteger class extension

extension NSInteger {
    /// The size on the iPhone 5
    / / / 🌶 "* pay attention to the order of operations to 60 in the i5 (30) is equivalent to - (60 i5 (30)) results for - (-) 30 or 60"
    ///
    /// -parameter size: the size of iPhone 5
    /// - Returns: isiPhone5 ? size : CGFloat(self)
    func i5(_ size: CGFloat) -> CGFloat {
        return isiPhone5 ? size : CGFloat(self)}/// The size of the iPhone 6 in zoom mode
    I6BigModel (-30) is equivalent to -(60.i6BigModel(-30)). The result is -(-30) or -60.
    ///
    /// -parameter size: iPhone 6 size in zoom mode
    /// - Returns: isiPhone6BigModel ? size : CGFloat(self)
    func i6BigModel(_ size: CGFloat) -> CGFloat {
        return isiPhone6BigModel ? size : CGFloat(self)}/// The size of the iPhone 6p in zoom mode
    I6PBigModel (-30) is equivalent to -(60.i6PBigModel(-30)). The result is -(-30) or -60.
    ///
    /// -parameter size: iPhone 6p size in zoom mode
    /// - Returns: isiPhone6PlusBigMode ? size : CGFloat(self)
    func i6PBigModel(_ size: CGFloat) -> CGFloat {
        return isiPhone6PlusBigMode ? size : CGFloat(self)}/// The size on the iPhone X
    / / / 🌶 "* pay attention to the order of operations - 60 ix (30) is equivalent to - (60. Ix (30)) results for - (-) 30 or 60"
    ///
    /// -parameter size: the size of iPhone X
    /// - Returns: isiPhoneX ? Size / 2.0: CGFloat(self)
    func ix(_ size: CGFloat) -> CGFloat {
        return isiPhoneX ? size : CGFloat(self)}/// ipad
    / / / 🌶 "* pay attention to the order of operations to 60. The device (30) is equivalent to - (60) device (30)) results for - (-) 30 or 60"
    ///
    /// -parameter size: specifies the size of the iPad
    /// - Returns: isIpad ? size : CGFloat(self)
    func ipad(_ size: CGFloat) -> CGFloat {
        return isIpad ? size : CGFloat(self)}/// scale width
    ///
    /// - Parameter size: origin width
    /// - Returns: The scaled width is not divided by 2.0
    func scaleW(a) -> CGFloat {
        return (screenWidth / 375 * CGFloat(self))}/// height result does not divide by 2.0
    ///
    /// - Parameter size: origin height
    /// - Returns: Height is not divided by 2.0 after scaling
    func scaleH(a) -> CGFloat {
        return (screenHeight / 667 * CGFloat(self))}}Copy the code

2.1.3.3 Adding CGFloat class extensions

extension CGFloat {
    
    /// The size on the iPhone 5
    / / / 🌶 "* pay attention to the order of operations to 60 in the i5 (30) is equivalent to - (60 i5 (30)) results for - (-) 30 or 60"
    ///
    /// -parameter size: the size of iPhone 5
    /// - Returns: isiPhone5 ? size : self
    func i5(_ size: CGFloat) -> CGFloat {
        return isiPhone5 ? size : self
    }
    
    /// The size of the iPhone 6 in zoom mode
    I6BigModel (-30) is equivalent to -(60.i6BigModel(-30)). The result is -(-30) or -60.
    ///
    /// -parameter size: iPhone 6 size in zoom mode
    /// - Returns: isiPhone6BigModel ? : self
    func i6BigModel(_ size: CGFloat) -> CGFloat {
        return isiPhone6BigModel ? size : self
    }
    
    /// The size of the iPhone 6p in zoom mode
    I6PBigModel (-30) is equivalent to -(60.i6PBigModel(-30)). The result is -(-30) or -60.
    ///
    /// -parameter size: iPhone 6p size in zoom mode
    /// - Returns: isiPhone6PlusBigMode ? size : self
    func i6PBigModel(_ size: CGFloat) -> CGFloat {
        return isiPhone6PlusBigMode ? size : self
    }
    
    /// The size on the iPhone X
    / / / 🌶 "* pay attention to the order of operations - 60 ix (30) is equivalent to - (60. Ix (30)) results for - (-) 30 or 60"
    ///
    /// -parameter size: the size of iPhone X
    /// - Returns: isiPhoneX ? size : self
    func ix(_ size: CGFloat) -> CGFloat {
        return isiPhoneX ? size : self
    }
    
    /// The size on the iPad
    / / / 🌶 "* pay attention to the order of operations to 60. The device (30) is equivalent to - (60) device (30)) results for - (-) 30 or 60"
    ///
    /// -parameter size: specifies the size of the iPad
    /// - Returns: isIpad ? size : self
    func ipad(_ size: CGFloat) -> CGFloat {
        return isIpad ? size : self
    }
    
    
    /// scale width
    ///
    /// - Parameter size: origin width
    /// - Returns: The scaled width is not divided by 2.0
    func scaleW(a) -> CGFloat {
        return (screenWidth / 375 * self)}/// scale height
    ///
    /// - Parameter size: origin height
    /// - Returns: Height is not divided by 2.0 after scaling
    func scaleH(a) -> CGFloat {
        return (screenHeight / 667 * self)}}Copy the code

2.1.3.4 Adding a Bool class extension

extension Bool {
    /// The size on the iPhone 5
    ///
    /// -parameter size: the size of iPhone 5
    /// - Returns: isiPhone5 ? size : self
    func i5(_ size: Bool) -> Bool {
        return isiPhone5 ? size : self
    }
    
    /// The size of the iPhone 6 in zoom mode
    ///
    /// -parameter size: iPhone 6 size in zoom mode
    /// - Returns: isiPhone6BigModel ? size : self
    func i6BigModel(_ size: Bool) -> Bool {
        return isiPhone6BigModel ? size : self
    }
    
    /// The size of the iPhone 6p in zoom mode
    ///
    /// -parameter size: iPhone 6p size in zoom mode
    /// - Returns: isiPhone6PlusBigMode ? size : self
    func i6PBigModel(_ size: Bool) -> Bool {
        return isiPhone6PlusBigMode ? size : self
    }
    
    /// The size on the iPhone X
    ///
    /// -parameter size: the size of iPhone X
    /// - Returns: isiPhoneX ? Size / 2.0: self
    func ix(_ size: Bool) -> Bool {
        return isiPhoneX ? size : self
    }
    
    /// ipad
    ///
    /// -parameter size: specifies the size of the iPad
    /// - Returns: isIpad ? size : self
    func ipad(_ size: Bool) -> Bool {
        return isIpad ? size : self}}Copy the code

2.2 Image adaptation

  • In a project there is often a requirement like this: as shown below, cut part of the stretch, the rest unchanged

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

img = [img resizableImageWithCapInsets:UIEdgeInsetsMake(0.13.0.55) resizingMode:UIImageResizingModeStretch];

self.resizableImgView.image = img;
Copy the code

Swift code is as follows

/// Stretch the image from the middle
    ///
    /// -parameter image
    /// - Returns: Returns after stretching
    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
    }
Copy the code
  • Tiled image: that is, one small image can be tiled into multiple small images

    The implementation code is as follows:

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

img = [img resizableImageWithCapInsets:UIEdgeInsetsMake(0.11.5.0.11) resizingMode:UIImageResizingModeTile];

self.resizableImgView.image = img;
Copy the code
  • Create images with solid colors
/// create images with solid colors
    ///
    /// -parameter color
    /// - Returns: images created with pure colors
    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(a)guard let context = ctx else { return UIImage() }
        context.setFillColor(color.cgColor)
        context.fill(rect)
        let theImage: UIImage? = UIGraphicsGetImageFromCurrentImageContext(a)UIGraphicsEndImageContext(a)return theImage ?? UIImage()}Copy the code

2.3 Word adaptation processing

2.3.1 Calculate the width and height according to the string

  • In principle, UILabel does not set the height, but automatically ADAPTS the height according to the text content. In this case, we often need to calculate the width and height of the entire String from the literal String.

The calculation method used for JIMU 1.0 is as follows:

extension String {

func calculateSize(_ size: CGSize, font: UIFont) -> CGSize {
        let paragraphStyle = NSMutableParagraphStyle(a)// 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
    }
}

Copy the code
// Calculate the height or width of the text regardless of the weight argument
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)
    }
}
Copy the code

2.3.2 UIColor conversion

extension UIColor {
    
    
    / / / RGB color
    ///
    /// - 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)
    }
    
    
    // hexadecimal color
    ///
    /// - Parameters:
    /// -rgb: RGB Int
    /// -alpha: transparency
    convenience init(hex rgb:Int, alpha:CGFloat = 1.0) {
        self.init(red: (rgb >> 16) & 0xFF, green: (rgb >> 8) & 0xFF, blue: rgb & 0xFF, alpha: alpha)
    }

    
    /// random color
    ///
    /// -parameter randomAlpha: specifies whether the transparency is random. The default is false
    /// - Returns: random color
    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)
    }
}

Copy the code

2.3.3

2.4 Special control adaptation

3. The latest IOS system adaptation problem

  • Apple official information:
  1. WWDC19 video
  2. Xcode 11 Beta download
  3. MacOS Catalina 10.15 Beta download

3.1 IOS 13 Adaptation

3.1.1 Soon to be obsolete LaunchImage

Since iOS 8, apple has introduced the LaunchScreen, which we can set up as the LaunchScreen. Of course, now you can also use LaunchImage to set up the LaunchImage. With LaunchImage, however, we have to provide launch images of various screen sizes to fit all devices, which is obviously not Flexible enough as Apple devices get bigger and bigger. LaunchScreen supports AutoLayout+SizeClass, so it’s easy to fit all kinds of screens.

  • Note ⚠️, starting in April 2020, all apps using the iOS13 SDK will be required to provide the LaunchScreen, and the LaunchImage will be phased out *.

3.1.2 Sign in with Apple – Provide third party login attention

If your app uses third-party logins, Then you may also need to add “Sign in with Apple” Sign in with Apple will be available for beta testing this summer. It will be required as an option for users in apps that support third-party sign-in when it is commercially available later this year.

  • How to integrate with this blog: Sign in with Apple

3.1.3 iOS 13 DeviceToken Changed

NSString *dt = [deviceToken description]; dt = [dt stringByReplacingOccurrencesOfString: @”<” withString: @””]; dt = [dt stringByReplacingOccurrencesOfString: @”>” withString: @””]; dt = [dt stringByReplacingOccurrencesOfString: @” ” withString: @””]; This code can no longer get the exact DeviceToken string when running on iOS 13. What iOS 13 gets from [DeviceToken Description] has changed.

  • The solution
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
    if(! [deviceToken isKindOfClass:[NSData class]]) return;
    const unsigned *tokenBytes = [deviceToken bytes];
    NSString *hexToken = [NSString stringWithFormat: @ "% 08x% 08x% 08x% 08x% 08x% 08x% 08x% 08x",
                          ntohl(tokenBytes[0]), ntohl(tokenBytes[1]), ntohl(tokenBytes[2]),
                          ntohl(tokenBytes[3]), ntohl(tokenBytes[4]), ntohl(tokenBytes[5]),
                          ntohl(tokenBytes[6]), ntohl(tokenBytes[7])];
    NSLog(@"deviceToken: % @ ",hexToken);
}

Copy the code

3.1.4 MPMoviePlayerController is no longer available on iOS 13

‘MPMoviePlayerController is no longer available. Use AVPlayerViewController in AVKit.’

  • Solution:

Since I can’t use it any more, I have to replace it. The alternative is the AVKit player.

3.1.5 Controller modalPresentationStyle default value changed

A look at the UIModalPresentationStyle enumeration definition shows that iOS 13 has a new enumeration value:

typedef NS_ENUM(NSInteger.UIModalPresentationStyle) {
    UIModalPresentationFullScreen = 0.UIModalPresentationPageSheet API_AVAILABLE(ios(3.2)) API_UNAVAILABLE(tvos),
    UIModalPresentationFormSheet API_AVAILABLE(ios(3.2)) API_UNAVAILABLE(tvos),
    UIModalPresentationCurrentContext API_AVAILABLE(ios(3.2)),
    UIModalPresentationCustom API_AVAILABLE(ios(7.0)),
    UIModalPresentationOverFullScreen API_AVAILABLE(ios(8.0)),
    UIModalPresentationOverCurrentContext API_AVAILABLE(ios(8.0)),
    UIModalPresentationPopover API_AVAILABLE(ios(8.0)) API_UNAVAILABLE(tvos),
    UIModalPresentationBlurOverFullScreen API_AVAILABLE(tvos(11.0)) API_UNAVAILABLE(ios) API_UNAVAILABLE(watchos),
    UIModalPresentationNone API_AVAILABLE(ios(7.0= -))1.UIModalPresentationAutomatic API_AVAILABLE(ios(13.0= -))2};Copy the code
  • The solution
  1. If you fully accept apple’s default, you don’t need to change any code.
  2. If you had been careful to set the modalPresentationStyle values, you wouldn’t have had this effect.
  3. For students want to get the default interactive, direct Settings are as follows: the self. ModalPresentationStyle = UIModalPresentationOverFullScreen;

The private _placeholderLabel attribute of the UITextField was disabled

  • Calling the following code on IOS 13 will cause a flash backout
[self.textField setValue:self.placeholderColor forKeyPath:@"_placeholderLabel.textColor"];
Copy the code

The following error message is printed:

‘Access to UITextField’s _placeholderLabel ivar is prohibited. This is an application bug’

  • Solution:
UITextFieldThere's an attributedPlaceholder property, and we can customize this rich text to get the result that we want.NSMutableAttributedString *placeholderString = [[NSMutableAttributedString alloc] initWithString:placeholder attributes:@{NSForegroundColorAttributeName : self.placeholderColor}];
_textField.attributedPlaceholder = placeholderString;
Copy the code

IOS 13 Using KVC to modify private attributes, risk Crash, use caution! Not all KVC can Crash, try!

3.1.7 UISearchBar Display Failure

  • The SearchBar is only 1px high
  1. After upgrading to iOS13, the SearchBar on UISearchController displays abnormally. After checking, it is found that the corresponding height is only 1px. The specific cause has not been found.
  2. The solution is to use KVO to listen for changes in the frame value and set the height to be displayed
  • Black line processing crash
  1. Before, in order to deal with the black line problem in the search box, UISearchBarBackground will be deleted after traversal. In iOS13, UI rendering will fail to crash;
  2. The workaround is to set layer.contents of UISearchBarBackground to nil
  • TabBar red dot offset
  1. If you set the position of the red dot by the image position on TabBar, on iOS13 you will see that the display position is moved to the far left. Traverse UITabBarButton subViews found only in the TabBar selected state can take to UITabBarSwappableImageView,
  2. The solution is to set the frame of the red dot using the position of the UITabBarButton

3.1.8 Dark Mode Dark Mode

Apps on iOS 13 are expected to support dark mode Use system colors and materials Create your own dynamic colors and images Leverage flexible infrastructure

The UI needs a new set of interactions

  • In iOS13 for UIViewController and UIView extends a new API – overrideUserInterfaceStyle, using the method, the official document said roughly:
  1. By setting the overrideUserInterfaceStyle attributes in order to make the view and its child views with specific UIUserInterfaceStyle. But if you want to get the current UIUserInterfaceStyle, need to convert traitCollection userInterfaceStyle.
  2. As far as possible on the use of UIViewController overrideUserInterfaceStyle properties. Use this property only when: (1) Using a specific style locally on a single view or small view hierarchy. (2) you want to use specific styles on the entire UIWindow and its ViewController and modal popup ViewController, and you don’t want to force changes to have styles on the entire application. (If you really want the entire application to have a certain style, don’t use it and instead set the UIUserInterfaceStyle key in info.plist.)
  3. When set on a normal UIView: This property affects only the characteristics of this view and its children. It does not affect any view controller or any other view controller’s child views.
  4. When set on UIWindow: This property affects rootViewController and therefore the overall view controller and view hierarchy. It also affects the interface that the Window mode comes out of.
  • It follows that,overrideUserInterfaceStyleIt’s not only going to affect you, it’s going to affect your subview, let’s do thatwindowIt will affect the wholewindowAll views and view controllers in, including modal jump out view controllers. And, as the documentation specifically emphasizes, you can set up the entire application just to use a certain style, either in code or in codeinfo.plistConfiguration keyUser Interface Style, the correspondingValueforLight/Dark.
if #available(iOS 13.0, *) { window? .overrideUserInterfaceStyle = .light; }Copy the code

3.1.8.1 Adapt to Dark Mode

  • The adaptation of Dark mode mainly includes the following aspects:
  1. Simulator Debug
  2. Images (assets)
  3. Color (color)
  4. Status Bar
3.1.8.1.1 Simulator debugging
  • To start the project, click Environment Overrides in the debug bar at the bottom of Xcode.
  • Turn on Interface Style and you can switch. The diagram below:
3.1.8.1.2 Image Adaptation
  • Image adaptation, mainly our local image resources adaptation, network pictures, or more cumbersome.
  • Picture adaptation is more convenient through Assets. Xcassets for picture management:
  1. Add an image set, rename it like “adaptimage”, select the image set;
  2. Select the Attributes Inspector;
  3. Replacing “None” with “Any,Dark”;
  4. You can set different pictures for different modes. Changing mode will automatically select different pictures
  • Of course picture adaptation, you can also directly use the judge current systemmodePersonally, I don’t like this way very much, because I also need to listen for changes in system mode, rewriteUITraitEnvironmentProtocol methodstraitCollectionDidChange(_:), let’s take a look at the protocol method:
/** Trait environments expose a trait collection that describes their environment. */
public protocol UITraitEnvironment : NSObjectProtocol {

    @available(iOS 8.0*),var traitCollection: UITraitCollection { get }

    /** To be overridden as needed to provide custom behavior when the environment's traits change. */
    @available(iOS 8.0*),func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?)
}

Copy the code
  • Finally, we just need to rewrite the agent when changing system mode:
func updateImageView(a) {
    let image = traitCollection.userInterfaceStyle == .light ? UIImage(named: "dark-ios") : UIImage(named: "white-ios")
    imageView.image = image
}

override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
    super.traitCollectionDidChange(previousTraitCollection)
    updateImageView()
}
Copy the code
3.1.8.1.3 Color Adaptation
  • There are three ways of color matching:
  • Methods a: it is throughAssets.xcassetsAdd aColor SetCurrently supported by the systemAcuity iOS11.0
extension UIColor {
    @available(iOS 11.0*),public /*not inherited*/ init? (named name:String) // load from main bundle

    @available(iOS 11.0*),public /*not inherited*/ init? (named name:String.in bundle: Bundle? , compatibleWith traitCollection:UITraitCollection?). }Copy the code

  • Method 2: code creates dynamic colorsinit(dynamicProvider: @escaping (UITraitCollection) -> UIColor)Currently supported by the systemIOS 13.0 or higher
/ / method 2
let titleColor = UIColor.init(dynamicProvider: { (trait) -> UIColor in
    return trait.userInterfaceStyle == .light ? UIColor.black : UIColor.white
})
btn.setTitleColor(titleColor, for: .normal)
Copy the code
  • Methods three: Like pictures, listen for mode shifts and overwritetraitCollectionDidChange(_:)Method, not recommended.
3.1.8.1.4 Status Bar Adaptation
  • The current status bar also adds a mode, from the previous two, to three, wheredefaultInstead of black content, it will automatically select the current display according to the system modelightContentordarkContent.
public enum UIStatusBarStyle : Int {
    case `default` // Automatically chooses light or dark content based on the user interface style

    @available(iOS 7.0*),case lightContent // Light content, for use on dark backgrounds

    @available(iOS 13.0*),case darkContent // Dark content, for use on light backgrounds
}
Copy the code
  • We can override the preferredStatusBarStyle get method when we use it:
override var preferredStatusBarStyle: UIStatusBarStyle{
    get{
        return .lightContent
    }
}
Copy the code

3.1.9 Mode popup default interaction changes

IOS 13’s presentViewController has a parallax effect by default, and modal out interfaces now slide back by default. Some pages must be confirmed to disappear, need to adapt. If the page height of your project is all screen size, the extra navigation height can be problematic.

/* Defines the presentation style that will be used for this view controller when it is presented modally. Set this property on the view controller to be presented, not the presenter. If this property has been set to UIModalPresentationAutomatic, reading it will always return a concrete presentation style. By default UIViewController resolves UIModalPresentationAutomatic to UIModalPresentationPageSheet, but other system-provided view controllers may resolve UIModalPresentationAutomatic to other concrete presentation Styles. Defaults to UIModalPresentationAutomatic on iOS starting in iOS 13.0, and UIModalPresentationFullScreen on previous versions. Defaults to UIModalPresentationFullScreen on all other platforms. */
@property(nonatomic,assign) UIModalPresentationStyle modalPresentationStyle API_AVAILABLE(ios(3.2));
Copy the code
  • Solution:
// Swift
self.modalPresentationStyle = .fullScreen
 
// Objective-C
self.modalPresentationStyle = UIModalPresentationFullScreen;
Copy the code

3.1.10 During App startup, some Views may not be able to obtain frame in real time

In order to optimize the startup speed, some views may not be able to obtain the correct frame in real time during App startup

  • The solution
// Only after executing the viewDidAppear method on UIViewController can we get the correct value. In viewDidLoad, the frame Size is 0, for example:
 [[UIApplication sharedApplication] statusBarFrame];
Copy the code

For more information on IOS changes, see: iOS13AdaptationTips

A young idler, an old beggar

Refer to the blog: www.jianshu.com/p/75f34462b…