控制器相關(guān)
導(dǎo)航欄
-
導(dǎo)航欄跟隨右滑手勢返回
// 第一個ViewController
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
// 這里一定要使用這個方法 否則會有問題
self.navigationController?.setNavigationBarHidden(true, animated: true)
}
// 第二個ViewController
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.setNavigationBarHidden(false, animated: true)
}
-
導(dǎo)航欄設(shè)置中間View
self.navigationItem.titleView = customView;
-
present時設(shè)置全屏
// Swift
navigationController.modalPresentationStyle = .fullScreen
// OC
navigationController.modalPresentationStyle = UIModalPresentationFullScreen;
TabbarController
push跳轉(zhuǎn)時隱藏tabbar
let nextVC = ALCourseListViewController()
nextVC.hidesBottomBarWhenPushed = true
self.navigationController?.pushViewController(nextVC, animated: true)
// 這樣back回來的時候振湾,tabBar會恢復(fù)正常顯示
self.hidesBottomBarWhenPushed = false
修改tabbar圖片和文字的顏色
self.tabBar.tintColor = UIColor.black
修改tabbar整體的背景色
self.tabBar.barTintColor = .white
設(shè)置tabbaritem的內(nèi)容
meNav.tabBarItem.title = ALTool.localizedString("mine")
meNav.tabBarItem.setTitleTextAttributes([NSAttributedString.Key.font: UIFont.systemFont(ofSize: 11)], for: .normal)
meNav.tabBarItem.titlePositionAdjustment = UIOffset.init(horizontal: 0, vertical: -6)
// 使用alwaysOriginal渣淤,強(qiáng)制渲染原圖戚丸。不使用tintColor覆蓋孩等。
meNav.tabBarItem.image = ALUIImage(named: "icon_tab_mine_gray")?.withRenderingMode(.alwaysOriginal)
meNav.tabBarItem.selectedImage = ALUIImage(named: "icon_tab_mine_light")?.withRenderingMode(.alwaysOriginal)
meNav.tabBarItem.tag = 2
// 設(shè)置tabbar項(xiàng)幕屹,并制定默認(rèn)顯示位置
self.setViewControllers([nav0, nav1, meNav], animated: false)
self.selectedIndex = 0
跳轉(zhuǎn)storyboard關(guān)聯(lián)的VC
// xxx 是UIStoryboard的名字满葛,不帶后綴昌罩;xxxVC 是視圖中Identity的StoryboardID
let destinationStoryboard = UIStoryboard(name:"xxx",bundle:nil)
let destinationViewController = destinationStoryboard.instantiateViewController(withIdentifier: "xxxVC") as! XXXViewController
self.navigationController?.pushViewController(destinationViewController, animated: true)
參考文獻(xiàn):
swift 使用多個storyBoard,進(jìn)行視圖跳轉(zhuǎn)
頂部切換tab
黑色模式鎖定
在target的info 中添加 User Interface Style
值為 Light
或 Dark
狀態(tài)欄
-
隱藏狀態(tài)欄
-
全局設(shè)置
在target的info中加入View controller-based status bar appearance
值為NO
再將General->Deployment Info中的Hide status bar
勾選
在視圖控制器中單獨(dú)設(shè)置
這種方法適合于只隱藏部分頁面的狀態(tài)欄阱穗。我們在需要隱藏 statusbar 的 ViewController 中添加如下代碼即可饭冬。
override var prefersStatusBarHidden: Bool {
return false
}
調(diào)節(jié)狀態(tài)欄顏色
在target的info中加入View controller-based status bar appearance
值為YES
在ViewController中,重寫以下方法即可
override var preferredStatusBarStyle: UIStatusBarStyle {
return .default // 默認(rèn)為黑色揪阶;lightContent:iOS 7.0以上可用昌抠,白色;darkContent:iOS13.0以上可用鲁僚,黑色炊苫;
}
View相關(guān)
webView相關(guān)
-
UIWebView 替換為 WKWebView
UIWebView:
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error API_DEPRECATED("No longer supported.", ios(2.0, 12.0));
WKWebView:
- (void)webView:(WKWebView *)webView didFailNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error;
UIAlertController
-
文本對齊方式
let subView1:UIView = alert.view.subviews[0]
let subView2 = subView1.subviews[0];
let subView3 = subView2.subviews[0];
let subView4 = subView3.subviews[0];
let subView5 = subView4.subviews[0];
//取title和message:
let title:UILabel = subView5.subviews[1] as! UILabel
let message:UILabel = subView5.subviews[2] as! UILabel
message.textAlignment = .left // 修改副標(biāo)題對齊方式
title.textAlignment = .center // 修改主標(biāo)題對齊方式
參考文獻(xiàn):
http://www.reibang.com/p/51a7896d8f1c
UITextView
-
設(shè)置placeHolder
一個包含placeholder的自定義UITextView
import UIKit
class CustomTextView: UITextView {
lazy var placeHolderLabel: UILabel = {
let label = UILabel()
label.numberOfLines = 0
label.backgroundColor = .clear
label.alpha = 0
label.tag = 999
label.lineBreakMode = .byWordWrapping
return label
}()
public var placeholder = ""
public var placeholderColor = UIColor.init(white: 0.8, alpha: 1)
init(frame: CGRect) {
super.init(frame: frame, textContainer: nil)
self.delegate = self
self.addSubview(placeHolderLabel)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func draw(_ rect: CGRect) {
placeHolderLabel.frame = CGRect.init(x: 8, y: 8, width: self.bounds.size.width - 16, height: 0)
placeHolderLabel.font = self.font
placeHolderLabel.textColor = self.placeholderColor
if placeholder.count > 0 {
placeHolderLabel.text = placeholder
placeHolderLabel.sizeToFit()
self.sendSubviewToBack(placeHolderLabel)
}
if self.text.count == 0 && placeholder.count > 0 {
self.viewWithTag(CWTagListManager.CustomTextViewTag)?.alpha = 1
}
super.draw(rect)
}
deinit {
NotificationCenter.default.removeObserver(self)
}
}
extension CustomTextView: UITextViewDelegate {
func textViewDidChange(_ textView: UITextView) {
if placeholder.count == 0 {
return
}
if self.text.count == 0 {
self.viewWithTag(999)?.alpha = 1
} else {
self.viewWithTag(999)?.alpha = 0
}
}
}
UIButton
-
文字換行
button.titleLabel?.numberOfLines = 0
button.titleLabel?.lineBreakMode = .byWordWrapping
-
左對齊
btn.contentHorizontalAlignment = .left
UILabel
-
富文本設(shè)置行間距
let label = UILabel(frame:CGRect(x:10, y:20, width:300, height:100))
//設(shè)置允許換行
label.numberOfLines = 0
//要顯示的文字
let str = "階段\n測試"
//通過富文本來設(shè)置行間距
let paraph = NSMutableParagraphStyle()
//將行間距設(shè)置為20
paraph.lineSpacing = 20
//樣式屬性集合
let attributes = [NSAttributedString.Key.paragraphStyle: paraph]
label.attributedText = NSAttributedString(string: str, attributes: attributes)
self.view.addSubview(label)
參考文獻(xiàn):
UIButton 文字換行的一種方案
UIScrollView
-
滑動到邊界后,不允許繼續(xù)拖動
scrollView.bounces = false
-
頂部有空白解決方法
if #available(iOS 11.0, *) {
self.contentInsetAdjustmentBehavior = .never
} else {
// Fallback on earlier versions
}
UITableView相關(guān)
UICollectionView相關(guān)
關(guān)于viewWithTag的坑
1冰沙、superview可以viewWithTag直接訪問到subview中對應(yīng)tag的控件侨艾,所以如果要標(biāo)記一個控件時,同一個superview下的subview拓挥,注意不要有存在沖突的相同tag的控件唠梨,建議根據(jù)view級數(shù)來定義,比如superview級的tag用100X,子View用200x侥啤,孫view用300x当叭,依次類推。
2愿棋、如果父view的tag和子view一樣科展,viewWithTag得到的會是父view,因?yàn)関iewWithTag得到的是最先設(shè)置tag為2000的那個控件(包含父view和子view)糠雨。
參考文獻(xiàn):
http://www.reibang.com/p/5040b6e0f5a0
CAShapeLayer和UIBezierPath
CAShapeLayer 是CALayer 的子類才睹。
let layer = CAShapeLayer()
layer.fillColor = UIColor.clear.cgColor
layer.strokeColor = CWCustomColor.colorWithRGB(red: 253, green: 198, blue: 81).cgColor
layer.lineWidth = 5
layer.lineJoin = .round
layer.lineCap = .round
layer.isHidden = true
CAShapeLayer 有一個特別好用的屬性,path。我們可以里用 UIBezierPath & CAShapeLayer的 .path 屬性琅攘,畫出我們?nèi)我庀M@示的圖形垮庐。并且搭配 CAShapeLayer 的 strokeBegin & strokeEnd 屬性∥肭伲可以做出一些比較炫酷的圖形動畫效果哨查。
UIBezierPath 專門是用來繪制路徑的,常和CAShapeLayer一起配合使用剧辐。
/// 使用路徑繪制 CAShape
/// 因?yàn)橛玫搅?貝塞爾曲線寒亥,所以就可以使用核心繪圖的一些參數(shù)。
/// 比如荧关,線寬溉奕、描邊、填充等忍啤。
func shapeLayerUserPath() {
let shapeLayer = CAShapeLayer()
// 創(chuàng)建路徑
let path = UIBezierPath()
path.move(to: CGPoint(x: 10, y: 80))
path.addLine(to: CGPoint(x: 100, y: 80))
path.addLine(to: CGPoint(x: 100, y: 180))
path.addLine(to: CGPoint(x: 10, y: 180))
path.addLine(to: CGPoint(x: 10, y: 80))
// [path closePath]; // 閉合路徑
// 設(shè)置 CAShapeLayer 的繪制路徑
shapeLayer.path = path.cgPath
// 有路徑了加勤,就可以設(shè)置填充顏色,線的樣式同波,描邊顏色等鳄梅。
shapeLayer.strokeColor = UIColor.purple.cgColor
// CAShapeLayer 如果是閉合路徑,那么默認(rèn)的填充顏色是黑色未檩。
// shapeLayer.fillColor = [UIColor orangeColor].CGColor;
shapeLayer.fillColor = UIColor.white.cgColor
shapeLayer.lineWidth = 10 // 線寬
shapeLayer.lineJoin = CAShapeLayerLineJoin(rawValue: "round") // 線頭樣式
shapeLayer.lineCap = CAShapeLayerLineCap(rawValue: "round") // 折現(xiàn)結(jié)合處樣式
view.layer.addSublayer(shapeLayer)
}
運(yùn)行效果:
當(dāng)然戴尸,UIBezierPath 能夠畫出什么圖形,那么 CAShapeLayer 就能顯示出多少中圖形讹挎。
- 畫曲線
private func addShareLayer() {
let layer = CAShapeLayer()
let path = UIBezierPath()
// 畫一條貝塞爾曲線
path.move(to: CGPoint.init(x: 0, y: UIScreen.main.bounds.height * 0.5 + 100))
path.addQuadCurve(to: CGPoint.init(x: UIScreen.main.bounds.width, y: UIScreen.main.bounds.height * 0.5 + 100), controlPoint: CGPoint.init(x: view.center.x, y: view.center.y))
// 設(shè)置曲線到 CAShapeLayer 的 path
layer.path = path.cgPath
// 實(shí)現(xiàn)線的基本屬性
layer.strokeColor = UIColor.purple.cgColor
layer.lineWidth = 5
layer.lineCap = .round
layer.fillColor = UIColor.white.cgColor
self.view.layer.addSublayer(layer)
}
運(yùn)行效果:
使用 CAShapeLayer 以動畫的方式繪制圖形
由于 CAShapeLayer 繼承自 CALayer校赤。CALayer 有可以搭配 CAAnimation 使用。所以可以使用 CAShapeLayer & UIBezierPath & CAAnimation 來產(chǎn)生比較酷炫的動畫效果筒溃。
主要是搭配 CAShapeLayer 的 strokeStart & strokeEnd 來實(shí)現(xiàn)比較炫酷的效果。
private func shapeLayerDrawRect() {
let layer = CAShapeLayer()
let path = UIBezierPath.init(rect: CGRect.init(x: 10, y: 200, width: 100, height: 100))
layer.path = path.cgPath
layer.strokeColor = UIColor.orange.cgColor
layer.fillColor = UIColor.white.cgColor
layer.lineWidth = 3
layer.lineCap = .round
self.view.layer.addSublayer(layer)
let anim = CABasicAnimation.init(keyPath: "strokeEnd")
anim.fromValue = 0
anim.toValue = 1
anim.repeatCount = MAXFLOAT
anim.duration = 3
anim.fillMode = .forwards
anim.isRemovedOnCompletion = false
layer.add(anim, forKey: nil)
}
運(yùn)行效果:
使用場景:
可以利用 CAShapeLayer 的動畫特性做一些提示類的動畫沾乘。由于 CAShapeLayer 不是 UIResponder 怜奖,所以,它不能接受事件翅阵。
它的作用歪玲,就是展示,也僅僅是展示掷匠。
可以利用 CAShapeLayer 的路徑動畫特性滥崩,做一些有功能性的動畫。例如:選擇答案后顯示對勾錯誤讹语。效果和代碼可以在CAShapeLayer 初探文章中查看钙皮。
參考文獻(xiàn):
Core Graphics 之 路徑的填充規(guī)則與混合模式
CAShapeLayer 初探
CAShapeLayer和DrawRect
- DrawRect:DrawRect屬于CoreGraphic框架,占用CPU,消耗性能大
- CAShapeLayer:CAShapeLayer屬于CoreAnimation框架短条,通過GPU來渲染圖形导匣,節(jié)省性能。動畫渲染直接提交給手機(jī)GPU茸时,不消耗內(nèi)存
override func draw(_ rect: CGRect) {
// 獲取當(dāng)前的圖形上下文
let context = UIGraphicsGetCurrentContext()
// 設(shè)置線條的屬性
// 1.設(shè)置線寬
context?.setLineWidth(lineWidth_p_DP)
// 2.設(shè)置線條的顏色
context?.setStrokeColor(UIColor.brown.cgColor)
// 3.填充顏色
context?.setFillColor(UIColor.brown.cgColor)
// 開始畫線,需要將起點(diǎn)移動到指定的point
context?.move(to: firstPoint_p_DP)
// 添加一根線到另一個點(diǎn) (兩點(diǎn)一線)
context?.addLine(to: secondPoint_p_DP)
context?.addLine(to: thirdPoint_p_DP)
// 閉合路徑,連線結(jié)束后會把起點(diǎn)和終點(diǎn)連起來
context?.closePath()
// 奇偶規(guī)則:從路徑覆蓋范圍內(nèi)的任意一點(diǎn)做一條射線(確保這條射線的長度要比路徑覆蓋范圍要大) , 如果與該射線相交的邊的數(shù)量為奇數(shù), 則該點(diǎn)是路徑的內(nèi)部點(diǎn), 反之該點(diǎn)則是路徑的外部點(diǎn)贡定。
// 非零環(huán)繞數(shù)原則:首先定義一個用于焦點(diǎn)統(tǒng)計的count值,然后從路徑覆蓋范圍內(nèi)的任意一點(diǎn)做一條射線(確保這條射線的長度要比路徑覆蓋范圍要大). 然后我們對每一條和該射線相交的路徑進(jìn)行統(tǒng)計, 統(tǒng)計規(guī)則是這樣的: 當(dāng)路徑是從右向左穿過射線的時候, count++, 當(dāng)路徑是從左向右穿過射線的時候, count--. 當(dāng)我們統(tǒng)計完所有相交的路徑后, 如果 count不為0, 則該點(diǎn)是內(nèi)部點(diǎn), 該點(diǎn)所在的封閉區(qū)域需要填充, 反之該點(diǎn)則是路徑的外部點(diǎn)
// 混合模式:混合模式是指在進(jìn)行繪制時如何使用繪制背景的方式可都!Quartz2D中使用默認(rèn)的混合方式缓待,并使用以下公式將背景畫和前景畫進(jìn)行結(jié)合:result = (alpha * foreground) + (1 - alpha) * background 、alpha表示顏色的不透明值
// 使用CGPathDrawingMode繪制模式繪制當(dāng)前路徑(fill :使用非零環(huán)繞路徑渲染規(guī)則渠牲;eoFill:奇偶渲染規(guī)則命斧;stroke:沿著路徑渲染一條線;fillStroke:先按照非零環(huán)繞進(jìn)行填充然后進(jìn)行繪制路徑嘱兼;eoFillStroke:先按照奇偶規(guī)則填充国葬,然后進(jìn)行繪制路徑)
mainPath?.drawPath(using: .fillStroke)
// 使用CGPathFillRule填充規(guī)則(winding 非零環(huán)繞規(guī)則;evenOdd 奇偶規(guī)則)
mainPath?.fillPath(using: .winding)
// 渲染圖形到上下文
context?.strokePath()
}
參考文獻(xiàn):
iOS CAShapeLayer 使用
layoutSubviews和drawRect
一芹壕、layoutSubviews在以下情況下會被調(diào)用:
1汇四、init初始化不會觸發(fā)layoutSubviews。
2踢涌、addSubview會觸發(fā)layoutSubviews通孽。
3、改變一個UIView的Frame會觸發(fā)layoutSubviews睁壁,當(dāng)然前提是frame的值設(shè)置前后發(fā)生了變化背苦。
4、滾動一個UIScrollView引發(fā)UIView的重新布局會觸發(fā)layoutSubviews潘明。
5行剂、旋轉(zhuǎn)Screen會觸發(fā)父UIView上的layoutSubviews事件。
6钳降、直接調(diào)用setNeedsLayout 或者 layoutIfNeeded厚宰。
二、drawRect在以下情況下會被調(diào)用:
1遂填、如果在UIView初始化時沒有設(shè)置rect大小铲觉,將直接導(dǎo)致drawRect不被自動調(diào)用。drawRect 掉用是在Controller->loadView, Controller->viewDidLoad 兩方法之后掉用的.所以不用擔(dān)心在 控制器中,這些View的drawRect就開始畫了.這樣可以在控制器中設(shè)置一些值給View(如果這些View draw的時候需要用到某些變量值).
2吓坚、該方法在調(diào)用sizeToFit后被調(diào)用撵幽,所以可以先調(diào)用sizeToFit計算出size。然后系統(tǒng)自動調(diào)用drawRect:方法礁击。
3盐杂、通過設(shè)置contentMode屬性值為UIViewContentModeRedraw逗载。那么將在每次設(shè)置或更改frame的時候自動調(diào)用drawRect:。
4况褪、直接調(diào)用setNeedsDisplay撕贞,或者setNeedsDisplayInRect:觸發(fā)drawRect:,但是有個前提條件是rect不能為0测垛。
以上1,2推薦捏膨;而3,4不提倡
參考文獻(xiàn):
https://blog.csdn.net/wangyanchang21/article/details/50774522
離屏渲染
油畫算法
圖層的繪制,遵循油畫算法
食侮。即按層繪制号涯。先繪制距離較遠(yuǎn)的場景,再繪制較近的場景并覆蓋較遠(yuǎn)的部分锯七。如下圖
這樣就不會使較遠(yuǎn)的物體擋住較近的物體链快。但是有一個局限,就是無法在較近的一層渲染完后眉尸,再回去修改較遠(yuǎn)的圖層域蜗,因?yàn)檩^遠(yuǎn)的圖層已經(jīng)被覆蓋了。這時候就涉及到了離屏渲染噪猾。
離屏渲染
對于上述有前后依賴的圖層(如全局剪切霉祸,陰影等),油畫算法無法滿足袱蜡。這時可以另開辟一個空間丝蹭,用于臨時渲染,渲染完成后再渲染到當(dāng)前的緩沖區(qū)上坪蚁。這個臨時渲染奔穿,就是離屏渲染
。
因?yàn)殡x屏渲染敏晤,需要開辟新的空間贱田,并且共享同一個上下文,還需要做上下文切換茵典,并且渲染完后還要進(jìn)行拷貝操作湘换。所以會消耗一定的資源,當(dāng)離屏渲染過多時统阿,則會導(dǎo)致GPU渲染時間過長而發(fā)生卡頓,所以應(yīng)該避免離屏渲染筹我。
如何避免離屏渲染
若想避免離屏渲染扶平,首先要知道如何檢測離屏渲染。在Simulator的Debug中打開Color Off-screen Rendered蔬蕊。
// 1. UIImageView
let imageView = UIImageView(frame: CGRect(x: 50, y: 100, width: 300, height: 200))
self.view.addSubview(imageView)
imageView.image = UIImage.init(named: "test.jpg")
// image + cornerRadius + masksToBounds 不會觸發(fā)離屏渲染
imageView.layer.cornerRadius = 10
imageView.layer.masksToBounds = true
// 觸發(fā)離屏渲染
imageView.backgroundColor = UIColor.green
// 添加一個空的UIView不會觸發(fā)離屏渲染
// imageView.addSubview(UIView(frame: CGRect(x: 0, y: 0, width: 10, height: 10)))
// 2. UIButton
let button = UIButton(type: .custom)
button.frame = CGRect(x: 50, y: 300 + 50, width: 300, height: 50)
self.view.addSubview(button)
button.setTitle("Test", for: .normal)
button.setTitleColor(UIColor.blue, for: .normal)
button.layer.cornerRadius = 10
button.layer.masksToBounds = true
// 觸發(fā)離屏渲染
button.backgroundColor = UIColor.green
// 觸發(fā)離屏渲染
button.setBackgroundImage(UIImage(named: "test.jpg"), for: .normal)
// 3. UIView
let view = UIView(frame: CGRect(x: 50, y: 400 + 50, width: 300, height: 50))
self.view.addSubview(view)
view.backgroundColor = UIColor.red
view.layer.cornerRadius = 10
view.layer.masksToBounds = true
// label如果被渲染结澄,則會觸發(fā)渲染哥谷,如果text為空不會被渲染
let label = UILabel(frame: CGRect(x: 10, y: 10, width: 1, height: 1))
label.text = "1"
view.addSubview(label)
根據(jù)上述方法測試,可以得到是否觸發(fā)離屏渲染的情況:
設(shè)置了cornerRadious+masksToBounds的:
- UIImageView設(shè)置圖片麻献,不會觸發(fā)们妥;
- UIView設(shè)置背景顏色,如果沒有subViews勉吻,不會觸發(fā)监婶;
- UILabel設(shè)置文字,且設(shè)置backgroundColor齿桃,會觸發(fā)惑惶;
- UIButton設(shè)置文字和背景,會觸發(fā)短纵;
其他會觸發(fā)離屏渲染的情況: - 使用了遮罩的layer(layer.mask)
- 需要進(jìn)行裁剪的layer(layer.masksToBounds / view.clipsToBounds)
- 設(shè)置了組透明度為 YES带污,并且透明度不為 1 的layer (layer.allowsGroupOpacity / layer.opacity)
- 添加了投影的 layer (layer.shadow),但如果設(shè)置了shadowPath香到,則系統(tǒng)已經(jīng)知道如何繪制陰影了鱼冀,不會觸發(fā)離屏渲染
- 采用了光柵化的 layer (layer.shouldRasterize),光柵化也可以優(yōu)化離屏渲染問題
- 繪制了文字的 layer (UILabel, CATextLayer, CoreText等)
- 使用了毛玻璃/高斯模糊
優(yōu)化離屏渲染問題
1悠就、避免使用裁切(masksToBounds)方式千绪,如果確保內(nèi)容不會溢出,則不宜使用masksToBounds理卑;
2翘紊、必須使用裁切時,盡量用最外層的view去裁切藐唠。因?yàn)椴们行枰獙λ械膌ayer和subviews所有圖層進(jìn)行裁切帆疟,越內(nèi)層的view,離屏渲染所需要的空間越大宇立。
3踪宠、提前切好需要的圓角,避免需要的時候再切妈嘹。
參考文獻(xiàn):
https://blog.bombox.org/2020-07-14/ios-offscreen-render/