@[TOC](IOS 屏幕適配(一)理論篇)ios
是iOS開發中引入的抽象單位,稱做點。開發過程當中全部基於座標系的繪製都是以 point 做爲單位,在iPhone 2G,3G,3GS的年代,point 和屏幕上的像素是徹底一一對應的,即 320 * 480 (points), 也是 320 * 480 (pixels)git
Rendered Pixels: 渲染像素, 以 point 爲單位的繪製最終都會渲染成 pixels,這個過程被稱爲光柵化。基於 point 的座標系乘以比例因子能夠獲得基於像素的座標系,高比例因子會使更多的細節展現,目前的比例因子會是 1x,2x,3xgithub
Physical Pixels: 物理像素,就是設備屏幕實際的像素swift
Physical Device: 設備屏幕的物理長度,使用英寸做爲單位。好比iPhone 4屏幕是3.5英寸,iPhone 5 是4英寸,iphone 6是4.7英寸,這裏的數字是指手機屏幕對角線的物理長度。實際上會是Physical Pixels的像素值(而不是Rendered Pixels的像素值)會渲染到該屏幕上, 屏幕會有 PPI(pixels-per-inch) 的特性,PPI 的值告訴你每英寸會有多少像素渲染。api
機型 | 屏幕寬高(point) | 渲染像素(pixel) | 物理像素(pixel) | 屏幕對角線長度(英寸) | 屏幕模式 |
---|---|---|---|---|---|
iPhone 2G,3G,3GS | 320 * 480 | 320 * 480 | 320 * 480 | 3.5(163PPI) | 1x |
iPhone 4, 4s | 320 * 480 | 640 * 960 | 640 * 960 | 3.5 (326PPI) | 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 (326PPI) | 2x |
iPhone 6 Plus, 6s Plus, 7 Plus | 414 * 736 | 1242 * 2208 | 1080 * 1920 | 5.5 (401PPI) | 3x |
機型 | 屏幕寬高(point) | 比例 | 像素密度(PPI) | 屏幕尺寸 | 型號代碼 | 發佈日 |
---|---|---|---|---|---|---|
iPhone 2g | 480×320 | 3:2 | 163ppi | 3.5 | iPhone1,1 | 2008.01 |
iPhone 3g | 480×320 | 3:2 | 163ppi | 3.5 | iPhone1,2 | 2008.06 |
iPhone 3gs | 480×320 | 3:2 | 163ppi | 3.5 | iPhone2,1 | 2009.06 |
iPhone 4 | 960×640 | 3:2 | 163ppi | 3.5 | iPhone3,一、iPhone3,二、iPhone3,3 | 2010.06 |
iPhone 4s | 960×640 | 3:2 | 326ppi | 3.5 | iPhone4,1 | 2011.10 |
iPhone 5 | 1136×640 | 16:9 | 326ppi | 4.0 | iPhone5,一、iPhone5,2 | 2012.09 |
iPhone 5c | 1136×640 | 16:9 | 326ppi | 4.0 | iPhone5,三、iPhone5,4 | 2013.09 |
iPhone 5s | 1136×640 | 16:9 | 326ppi | 4.0 | iPhone6,一、iPhone6,2 | 2013.09 |
iPhone 6 | 1334×750 | 16:9 | 401ppi | 4.7 | iPhone7,2 | 2014.09 |
iPhone 6 plus | 1920×1080 | 16:9 | 401ppi | 5.5 | iPhone7,1 | 2014.09 |
iPhone 6s | 1334×750 | 16:9 | 401ppi | 4.7 | iPhone8,2 | 2015.09 |
iPhone 6s plus | 1920×1080 | 16:9 | 401ppi | 5.5 | iPhone8,1 | 2015.09 |
iPhone 5 SE | 1136×640 | 16:9 | 401ppi | 4.0 | iPhone8,4 | 2016.03 |
iPhone 7 | 1334×750 | 16:9 | 401ppi | 4.7 | iPhone9,一、iPhone9,3 | 2016.09 |
iPhone 7 plus | 1920×1080 | 16:9 | 401ppi | 5.5 | iPhone9,二、iPhone9,4 | 2016.09 |
iPhone 8 | 1334×750 | 16:9 | 401ppi | 4.7 | iPhone10,一、iPhone10,4 | 2017.09 |
iPhone 8 plus | 1920×1080 | 16:9 | 401ppi | 5.5 | iPhone10,二、iPhone10,5 | 2017.09 |
iPhone X | 2436×1125 | 18:9 | 458ppi | 5.8 | iPhone10,三、iPhone10,6 | 2017.09 |
iPhone XS | 2436×1125 | 18:9 | 458ppi | 5.8 | iPhone11,2 | 2018.09 |
iPhone XS Max | 2688×1242 | 18:9 | 458ppi | 6.5 | iPhone11,四、iPhone11,6 | 2018.09 |
iPhone XR | 1792×828 | 19.5:9 | 326ppi | 6.1 | iPhone11,8 | 2018.09 |
機型 | 屏幕寬高(point) | 屏幕模式(Scale) | 物理像素(pixel) | 比例 | 像素密度(PPI) | 屏幕尺寸 | 型號代碼 | 發佈日 |
---|---|---|---|---|---|---|---|---|
iPad | 1024×768 | @1x | 1024×768 | 4:3 | 163ppi | 9.7 | iPad1,1 | 2010.01 |
iPad 2 | 1024×768 | @1x | 1024×768 | 4:3 | 163ppi | 9.7 | iPad2,一、iPad2,二、iPad2,三、iPad2,4 | 2011.03 |
iPad 3(New) | 1024×768 | @2x | 2048×1536 | 4:3 | 264ppi | 9.7 | iPad3,一、iPad3,二、iPad3,3 | 2012.03 |
iPad 4 | 1024×768 | @2x | 2048×1536 | 4:3 | 264ppi | 9.7 | iPad3,四、iPad3,五、iPad3,6 | 2012.10 |
iPad 5 | 1024×768 | @2x | 2048×1536 | 4:3 | 264ppi | 9.7 | iPad6,十一、iPad6,12 | 2017.03 |
機型 | 屏幕寬高(point) | 屏幕模式(Scale) | 物理像素(pixel) | 比例 | 像素密度(PPI) | 屏幕尺寸 | 型號代碼 | 發佈日 |
---|---|---|---|---|---|---|---|---|
iPad Air | 1024×768 | @2x | 2048×1536 | 4:3 | 264ppi | 9.7 | iPad4,一、iPad4,二、iPad4,3 | 2013.10 |
iPad Air 2 | 1024×768 | @2x | 2048×1536 | 4:3 | 264ppi | 9.7 | iPad5,三、iPad5,4 | 2014.10 |
機型 | 屏幕寬高(point) | 屏幕模式(Scale) | 物理像素(pixel) | 比例 | 像素密度(PPI) | 屏幕尺寸 | 型號代碼 | 發佈日 |
---|---|---|---|---|---|---|---|---|
iPad Pro 12.9-inch | 1366×1024 | @2x | 2732×2048 | 4:3 | 264ppi | 12.9 | iPad6,七、iPad6,8 | 2015.09 |
iPad Pro 9.7-inch | 1024×768 | @2x | 2048×1536 | 4:3 | 264ppi | 9.7 | iPad6,三、iPad6,4 | 2016.03 |
iPad Pro 12.9-inch 2 | 1366×1024 | @2x | 2732×2048 | 4:3 | 264ppi | 12.9 | iPad7,一、iPad7,2 | 2017 |
iPad Pro 10.5 | 1112×834 | @2x | 2224×1668 | 4:3 | 264ppi | 10.5 | iPad7,三、iPad7,4 |
機型 | 屏幕寬高(point) | 屏幕模式(Scale) | 物理像素(pixel) | 比例 | 像素密度(PPI) | 屏幕尺寸 | 型號代碼 | 發佈日 |
---|---|---|---|---|---|---|---|---|
iPad mini | 1024×768 | @1X | 1024×768 | 4:3 | 163 | 7.9 | iPad2,五、iPad2,六、iPad2,7 | 2012.10 |
iPad mini 2 | 1024×768 | @2X | 2048×1536 | 4:3 | 326 | 7.9 | iPad4,五、iPad4,六、iPad4,7 | 2013.10 |
iPad mini 3 | 1024×768 | @2X | 2048×1536 | 4:3 | 326 | 7.9 | iPad4,七、iPad4,八、iPad4,9 | 2014.10 |
iPad mini 4 | 1024×768 | @2X | 2048×1536 | 4:3 | 326 | 7.9 | iPad5,一、iPad5,2 | 2015.09 |
機型 | 屏幕寬高(point) | 屏幕模式(Scale) | 物理像素(pixel) | 比例 | 像素密度(PPI) | 屏幕尺寸 | 型號代碼 | 發佈日 |
---|---|---|---|---|---|---|---|---|
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 |
屏幕模式,描述的就是屏幕中一個點有多少個 Rendered Pixels 渲染,對於2倍屏(又稱 Retina 顯示屏),會有 2 * 2 = 4 個像素的面積渲染,對於3倍屏(又稱 Retina HD 顯示屏),會有 3 * 3 = 9 個像素的面積渲染。網絡
iOS 開發中,全部控件的座標以及控件大小都是以點爲單位的,假如我在屏幕上須要展現一張 20 * 20 (單位:point)大小的圖片,那麼設計師應該怎麼給我圖呢?這裏就會用到屏幕模式的概念,若是屏幕是 2x,那麼就須要提供 40 * 40 (單位: pixel)大小的圖片,若是屏幕是 3x,那麼就提供 60 * 60 大小的圖片,且圖片的命名須要遵照如下規範: Standard:<device_modifier>.<filename_extension> High resolution:@2x<device_modifier>.<filename_extension> High HD resolution:@3x<device_modifier>.<filename_extension>app
ImageName: 圖片名字,根據場景命名 device_modifier: 可選,能夠是 ~ipad 或者 ~iphone, 當須要爲 iPad 和 iPhone 分別指定一套圖時須要加上此字段 filename_extension: 圖片後綴名,iOS中使用 png 圖片dom
例如: MyImage.png - 1x 顯示屏自動加載的圖片版本 MyImage@2x.png - 2x 顯示屏自動加載的圖片版本 MyImage@3x.png - 3x 顯示屏自動加載的圖片版本 MyImage@2x~iphone.png - 2x iPhone 和 iPod touch 顯示屏自動加載的圖片版本 MyImage@3x~iphone.png - 3x iPhone and iPod 顯示屏自動加載的圖片版本iphone
一個基本思路是:ide
- 選擇一種尺寸做爲設計和開發基準;
- 定義一套適配規則,自動適配剩下兩種尺寸;
- 特殊適配效果給出設計效果。
更多詳情能夠參考這篇文章:手機淘寶的設計方案
- 第一步,視覺設計階段,設計師按寬度750px(iPhone 6)作設計稿,除圖片外全部設計元素用矢量路徑來作。設計定稿後在750px的設計稿上作標註,輸出標註圖。同時等比放大1.5倍生成寬度1125px的設計稿,在1125px的稿子裏切圖。
- 第二步,輸出兩個交付物給開發工程師:一個是程序用到的@3x切圖資源,另外一個是寬度750px的設計標註圖。
- 第三步,開發工程師拿到750px標註圖和@3x切圖資源,完成iPhone 6(375pt)的界面開發。此階段不能用固定寬度的方式開發界面,得用自動佈局(auto layout),方便後續適配到其它尺寸。
- 第四步,適配調試階段,基於iPhone 6的界面效果,分別向上向下調試iPhone 6 plus(414pt)和iPhone 5S及如下(320pt)的界面效果。由此完成大中小三屏適配。
當面對大中小三種屏幕須要適配的時候,很容易想到先作好一種屏幕,再去適配剩下兩種屏幕。第一個決定是到底以哪一種屏幕做爲設計和開發的基準尺寸。咱們選擇中間尺寸的iPhone 6(750px/375pt)做爲基準,基於幾個緣由:
- 從中間尺寸向上和向下適配的時候界面調整的幅度最小。375pt下的設計效果適配到414pt和320pt誤差不會太大。假設以414pt爲基準作出很優雅的設計,到320pt可能元素之間比例就不是那麼回事了,好比圖片和文字之間視覺比例可能失調。
- iPhone 6 plus有兩種顯示模式,標準模式分辨率爲1242x2208,放大模式分辨率爲1125x2001(即iPhone 6的1.5倍)。可見官方系統裏iPhone 6和iPhone 6 plus分辨率之間就存在1.5倍的倍率關係。不少狀況下這兩種尺寸能夠用1.5倍直接等比適配。
- 1242x2208這個奇葩的數值是蘋果官方都不肯意公開宣傳的一個分辨率,不便於記憶和計算柵格。640x1136雖然是普遍應用的一個分辨率,可是大屏時代依然以小尺寸爲設計基準顯然不合時宜,設計師會停留在小屏的視角作設計. 因此,iPhone6的750x1334是最適合基準尺寸
控件彈性指的是,navigation、cell、bar等適配過程當中垂直方向上高度不變;水平方向寬度變化時,經過調整元素間距或元素右對齊的方式實現自適應。這樣屏幕越大,在垂直方向上能夠顯示更多內容,發揮大屏幕的優點。
//以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);
}];
複製代碼
橫屏下,水平方向適配函數
/// 設備橫屏下,水平方向適配·
///
/// - 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
}
複製代碼
/// 獲取設備型號
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
}
}
複製代碼
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
複製代碼
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))
}
}
複製代碼
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)
}
}
複製代碼
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
}
}
複製代碼
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()
}
複製代碼
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)
}
}
複製代碼
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)
}
}
複製代碼
從 iOS 8 的時候,蘋果就引入了 LaunchScreen,咱們能夠設置 LaunchScreen來做爲啓動頁。固然,如今你還可使用LaunchImage來設置啓動圖。不過使用LaunchImage的話,要求咱們必須提供各類屏幕尺寸的啓動圖,來適配各類設備,隨着蘋果設備尺寸愈來愈多,這種方式顯然不夠 Flexible。而使用 LaunchScreen的話,狀況會變的很簡單, LaunchScreen是支持AutoLayout+SizeClass的,因此適配各類屏幕都不在話下。
若是你的應用使用了第三方登陸,那麼你可能也須要加下 「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.
NSString *dt = [deviceToken description]; dt = [dt stringByReplacingOccurrencesOfString: @"<" withString: @""]; dt = [dt stringByReplacingOccurrencesOfString: @">" withString: @""]; dt = [dt stringByReplacingOccurrencesOfString: @" " withString: @""]; 這段代碼運行在 iOS 13 上已經沒法獲取到準確的DeviceToken字符串了,iOS 13 經過[deviceToken description]獲取到的內容已經變了。
- (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); } 複製代碼
'MPMoviePlayerController is no longer available. Use AVPlayerViewController in AVKit.'
既然不能再用了,那隻能換掉了。替代方案就是AVKit裏面的那套播放器。
查閱了下 UIModalPresentationStyle枚舉定義,赫然發現iOS 13新加了一個枚舉值:
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,
};
複製代碼
- 若是你徹底接受蘋果的這個默認效果,那就不須要去修改任何代碼。
- 若是,你原來就比較細心,已經設置了modalPresentationStyle的值,那你也不會有這個影響。
- 對於想要找回原來默認交互的同窗,直接設置以下便可: self.modalPresentationStyle = UIModalPresentationOverFullScreen;
[self.textField setValue:self.placeholderColor forKeyPath:@"_placeholderLabel.textColor"];
複製代碼
打印錯誤信息以下:
'Access to UITextField's _placeholderLabel ivar is prohibited. This is an application bug'
UITextField有個attributedPlaceholder的屬性,咱們能夠自定義這個富文原本達到咱們須要的結果。
NSMutableAttributedString *placeholderString = [[NSMutableAttributedString alloc] initWithString:placeholder attributes:@{NSForegroundColorAttributeName : self.placeholderColor}];
_textField.attributedPlaceholder = placeholderString;
複製代碼
iOS 13 經過 KVC 方式修改私有屬性,有 Crash 風險,謹慎使用!並非全部KVC都會Crash,要嘗試!
- 升級到iOS13,UISearchController上的SearchBar顯示異常,查看後發現對應的高度只有1px,目前沒找到具體致使的緣由,
- 解決辦法是: 使用KVO監聽frame值變化後設置去應該顯示的高度
- 以前爲了處理搜索框的黑線問題會遍歷後刪除UISearchBarBackground,在iOS13會致使UI渲染失敗crash;
- 解決辦法是: 設置UISearchBarBackground的layer.contents爲nil
- 若是以前有經過TabBar上圖片位置來設置紅點位置,在iOS13上會發現顯示位置都在最左邊去了。遍歷UITabBarButton的subViews發現只有在TabBar選中狀態下才能取到UITabBarSwappableImageView,
- 解決辦法是: 修改成經過UITabBarButton的位置來設置紅點的frame
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
UI 須要出一套新交互
- 經過設置overrideUserInterfaceStyle屬性以使該視圖及其子視圖具備特定的UIUserInterfaceStyle。但若是想要獲取當前的UIUserInterfaceStyle,須要改用traitCollection.userInterfaceStyle。
- 儘量使用UIViewController上的overrideUserInterfaceStyle屬性。僅在如下時間使用此屬性: (1) 在單個視圖或小視圖層次結構上局部使用特定樣式。 (2) 您但願在整個UIWindow及其視圖控制器和模態彈出的ViewController上使用特定樣式,且不但願強制更改整個應用程序具備樣式。 (若是您確實但願整個應用程序具備某種樣式,請不要使用它,而是在Info.plist中設置UIUserInterfaceStyle鍵。)
- 當設置在普通的UIView上時: 此屬性僅影響此視圖及其子視圖的特徵。 它不會影響任何視圖控制器或其餘視圖控制器的子視圖。
- 在UIWindow上設置時: 此屬性會影響rootViewController,從而影響整個視圖控制器和視圖層次結構。 它還會影響該window模態出來的界面。
overrideUserInterfaceStyle
不只會影響本身,還會影響本身的子視圖,換作window
就會影響整個window
中的全部視圖及視圖控制器,包括模態跳轉出來的視圖控制器。 並且,文檔中也特別強調了,你能夠設置整個應用程序只是用某種樣式,具體方法能夠經過代碼,也能夠經過info.plist
配置鍵User Interface Style
,對應的Value
爲Light/Dark
。if #available(iOS 13.0, *) {
window?.overrideUserInterfaceStyle = .light;
}
複製代碼
- 模擬器調試(simulator debug)
- 圖片(assets)
- 顏色(color)
- 狀態欄(status bar)
- 添加一個image set,重命名如"adaptimage",選中該image set;
- 選中Attributes Inspector;
- 將Appearances由"None"改成"Any,Dark";
- 不一樣模式下設置不一樣圖片便可,mode 改變會自動選擇不一樣的圖片
![]()
mode
的方式進行區分,就我我的而言不是很喜歡這種方式,由於還須要監聽系統模式的變化,重寫UITraitEnvironment
協議方法traitCollectionDidChange(_:)
,咱們先看下協議方法:/** 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?)
}
複製代碼
func updateImageView() {
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()
}
複製代碼
Assets.xcassets
添加一個Color Set
,目前系統支持≥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?)
}
複製代碼
init(dynamicProvider: @escaping (UITraitCollection) -> UIColor)
,目前系統支持≥iOS 13.0
// 方法二
let titleColor = UIColor.init(dynamicProvider: { (trait) -> UIColor in
return trait.userInterfaceStyle == .light ? UIColor.black : UIColor.white
})
btn.setTitleColor(titleColor, for: .normal)
複製代碼
traitCollectionDidChange(_:)
方法,不推薦這種。default
由以前的黑色內容,變成了會根據系統模式,自動選擇當前展現lightContent
仍是darkContent
。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
}
複製代碼
override var preferredStatusBarStyle: UIStatusBarStyle{
get{
return .lightContent
}
}
複製代碼
iOS 13 的
presentViewController
默認有視差效果,模態出來的界面如今默認都下滑返回。 一些頁面必需要點確認才能消失的,須要適配。若是項目中頁面高度所有是屏幕尺寸,那麼多出來的導航高度會出現問題。
/* 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));
複製代碼
// Swift
self.modalPresentationStyle = .fullScreen
// Objective-C
self.modalPresentationStyle = UIModalPresentationFullScreen;
複製代碼
多是爲了優化啓動速度,App 啓動過程當中,部分View可能沒法實時獲取到正確的frame
// 只有等執行完 UIViewController 的 viewDidAppear 方法之後,才能獲取到正確的值,在viewDidLoad等地方 frame Size 爲 0,例如:
[[UIApplication sharedApplication] statusBarFrame];
複製代碼
更多關於IOS的變化參考:iOS13AdaptationTips
少壯不努力,老大徒悲傷
參考博客:www.jianshu.com/p/75f34462b…