UI相关界面
| 手机型号 | 屏幕尺寸 | 屏幕密度 | 屏幕宽高(pt) | 屏幕分辨率(px) | 倍图 | 屏幕比例 |
|---|---|---|---|---|---|---|
| 4/4s | 3.5inch | 326 ppi | 320*480pt | 640*960px | @2x | 3:2 |
| 5/5c/5s/se(第一代) | 4.0inch | 326 ppi | 320*568pt | 640*1136px | @2x | 16:9 |
| 6/6s/7/8/se(第二代) | 4.7inch | 326 ppi | 375*667pt | 750*1334px | @2x | 16:9 |
| 6+/6s+/7+/8+ | 5.5inch | 401 ppi | 414*736pt | 1242 * 2208px | @3x | 16:9 |
| X/XS/11Pro | 5.8inch | 458 ppi | 375*812pt | 1125* 2436px | @3x | 19.5:9 |
| XR/11 | 6.1inch | 326 ppi | 414*896pt | 828* 1792px | @2x | 19.5:9 |
| Xs Max/11Pro Max | 6.5inch | 458 ppi | 414*896pt | 1242* 2688px | @3x | 19.5:9 |
| iPhone12 mini/13 mini | 5.4inch | 476 ppi | 360*780pt | 1080* 2340px | @3x | 19.5:9 |
| iPhone12/12Pro/13/13Pro/14 | 6.1inch | 460 ppi | 390*844pt | 1170* 2532px | @3x | 19.5:9 |
| iPhone 12 Pro Max /13 Pro Max/14 Plus | 6.7inch | 458 ppi | 428*926pt | 1284* 2778px | @3x | 19.5:9 |
| iPhone14 Pro | 6.1inch | 460 ppi | 393*852pt | 1179* 2556 | @3x | 19.5:9 |
| iPhone14Pro Max | 6.7inch | 460 ppi | 430*932pt | 1290* 2796px | @3x | 19.5:9 |
APP内如果要支持HTTP网络请求,在info.plist文件中添加
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>2
3
4
5
安全区域
之前状态栏高度:有刘海屏iPhone为44,无刘海屏为20。从 iOS 14 系统开始有刘海屏的iPhone状态栏高度不一定是44了。**安全区域的高度和状态栏的高度不一定是一样的,比如iPhone 14 Pro。**比如:
iPhoneXR,iPhone11状态栏高度为48,iPhone X,iPhone 11 Pro,iPhone 11 Pro Max,iPhone 12 mini状态栏高度44,iPhone 12,iPhone 12 Pro,iPhone 12 Pro Max,状态栏高度为47
在 iOS 13.0 之前,我们可以通过 UIApplication 单例中的 statusBarFrame 属性获取状态状态栏改度
UIApplication.shared.statusBarFrame.size.height在 iOS 13.0 之后,statusBarFrame 属性被废弃⚠️了。API 更换为 UIStatusBarManager 类中的 statusBarFrame 进行获取
if #available(iOS 13.0, *) {
let s = UIApplication.shared.windows.first?.windowScene?.statusBarManager?.statusBarFrame.height
}2
3
- 项目中常用的导航栏高度、状态栏高度
public let navHeight = statusBarHeight + 44.0 //导航栏高度 44 + 状态栏高度 <安全区域高度跟状态栏高度不一定一致,比如iPhone14 Pro>
//MARK: 状态栏高度
var statusBarHeight: CGFloat {
get {
var statusHeight: CGFloat = UIApplication.shared.statusBarFrame.height
if #available(iOS 13.0, *) {
guard let scene = UIApplication.shared.windows.first?.windowScene else { return 0 }
statusHeight = scene.statusBarManager?.statusBarFrame.height ?? 0
}
return statusHeight
}
}
//MARK: 安全区域高度
var safeAreaTop: CGFloat {
get {
if #available(iOS 13.0, *) {
let scene = UIApplication.shared.connectedScenes.first
guard let windowScene = scene as? UIWindowScene else { return 0 }
guard let window = windowScene.windows.first else { return 0 }
return window.safeAreaInsets.top
} else if #available(iOS 11.0, *) {
guard let window = UIApplication.shared.windows.first else { return 0 }
return window.safeAreaInsets.top
}
return 0;
}
}
//MARK: 安全区域底部高度
var safeAreaBottom: CGFloat {
get {
if #available(iOS 13.0, *) {
let scene = UIApplication.shared.connectedScenes.first
guard let windowScene = scene as? UIWindowScene else { return 0 }
guard let window = windowScene.windows.first else { return 0 }
return window.safeAreaInsets.bottom
} else if #available(iOS 11.0, *) {
guard let window = UIApplication.shared.windows.first else { return 0 }
return window.safeAreaInsets.bottom
}
return 0;
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
- 状态栏高度宏定义
#define statusHeight \
({CGFloat statusBarHeight = 0.0;\
if (@available(iOS 13.0, *)) {\
statusBarHeight = [UIApplication sharedApplication].windows.firstObject.windowScene.statusBarManager.statusBarFrame.size.height;\
} else { \
statusBarHeight = [UIApplication sharedApplication].statusBarFrame.size.height;\
}\
(statusBarHeight);\
})2
3
4
5
6
7
8
9
禁用 夜间/深色模式
info.plist文件中添加只支持浅色模式,info.plist 使用 Property List展示 Key 是Appearance。
<key>UIUserInterfaceStyle</key>
<string>Light</string>2
- 单个页面设置显示模式
if #available(iOS 13.0, *) {
self.overrideUserInterfaceStyle = .light//dark或light
}2
3
- 判断当前显示模式
if #available(iOS 13.0, *) {
let userinterFaceStyle = self.traitCollection.userInterfaceStyle
switch userinterFaceStyle {
case UIUserInterfaceStyle.dark://深色
break
case UIUserInterfaceStyle.light://浅色
break
case UIUserInterfaceStyle.unspecified://未明确
break
default:
break
}
}2
3
4
5
6
7
8
9
10
11
12
13
UIView
- Runloop渲染的过程
/// Render Loop 过程
/// 过程一:更新约束
func updateConstraints();
func setNeedsUpdateConstraints();
func updateConstraintsIfNeeded();
/// 过程二:Layout 调整
func layoutSubviews();
func setNeedsLayout(); //,等待下一个更新周期再更新视图。会触发layoutsubview方法。
func layoutIfNeeded(); // 强制视图立即更新其布局
/// 过程三:渲染与展示
func draw(_:);
func setNeedsDisplay();2
3
4
5
6
7
8
9
10
11
12
13
14
- 设置背景渐变色
func setGradientBackgroundColor(_ colors: [CGColor], axis: NSLayoutConstraint.Axis = .horizontal, locations: [NSNumber] = [0.0, 1.0], cornerRadius: CGFloat = 0.0) -> Void {
let startPoint = CGPoint.init(x: 0.0, y: 0.0)
var endPoint = CGPoint.init(x: 1.0, y: 0.0)
if axis == .vertical {
endPoint = CGPoint.init(x: 0.0, y: 1.0)
}
let gradientLayer = CAGradientLayer.init()
gradientLayer.frame = bounds
gradientLayer.startPoint = startPoint
gradientLayer.endPoint = endPoint
gradientLayer.locations = locations
gradientLayer.colors = colors
gradientLayer.cornerRadius = cornerRadius
layer.addSublayer(gradientLayer)
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- 圆角 + 阴影
func setShadow(radius: CGFloat = 8 ,_ shadowColor: UIColor = UIColor(r:128, g: 128, b: 128,a: 1),_ shadowOffset: CGSize = .zero, _ shadowRadius: CGFloat = 3,_ shadowOpacity: Float = 0.3) {
self.layer.cornerRadius = radius
self.layer.shadowColor = shadowColor.cgColor //阴影颜色
self.layer.shadowOffset = shadowOffset //阴影的大小,shadow 在 X 和 Y 轴 上延伸的方向,width往右 height往下是正
self.layer.shadowRadius = shadowRadius //阴影的扩散范围,相当于blur radius,也是shadow的渐变距离,从外围开始,往里渐变shadowRadius距离
self.layer.shadowOpacity = shadowOpacity //阴影的不透明度
}2
3
4
5
6
7
- 模糊 毛玻璃效果
func setEffect() -> UIView{
let blurEffect = UIBlurEffect(style: UIBlurEffect.Style.dark)
let blurView = UIVisualEffectView(effect: blurEffect)
blurView.frame.size = CGSize(width: self.frame.size.width, height: self.frame.size.height)
blurView.layer.masksToBounds = true
return blurView
}2
3
4
5
6
7
- 获取控件相对屏幕的坐标位置
let window = UIApplication.shared().delegate.window
let y = tapView.convert(tapView.bounds, to:window)2
UILabel
- 设置文字渐变自定义一个UILabel,重写
drawRect方法,在当前方法中使用渐变
- (void)drawRect:(CGRect)rect{
[super drawRect:rect];
CGContextRef context = UIGraphicsGetCurrentContext();
// 获取文字mask
[self.text drawInRect:self.bounds withAttributes:@{NSFontAttributeName : self.font}];
CGImageRef textMask = CGBitmapContextCreateImage(context);
// 清空画布
CGContextClearRect(context, rect);
// 设置蒙层
CGContextTranslateCTM(context, 0.0, self.bounds.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
CGContextClipToMask(context, rect, textMask);
// 绘制渐变
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
// 渐变的距离 0 - 1
CGFloat locations[] = {0, 1};
// 颜色矩阵
CGFloat colors[] = {1.0,0.0,0.0,1.0,
0.0,1.0,0.0,1.0};
CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, colors, locations, 2);
CGPoint start = CGPointMake(0, 0);
CGPoint end = CGPointMake(0, self.bounds.size.height);
CGContextDrawLinearGradient(context, gradient, start, end, kCGGradientDrawsBeforeStartLocation);
// 释放
CGColorSpaceRelease(colorSpace);
CGGradientRelease(gradient);
CGImageRelease(textMask);
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
- 字体粗细
titleLabel.font = UIFont.systemFont(ofSize: 17.0)
titleLabel.font = UIFont.systemFont(ofSize: 17.0, weight: .heavy)
// 字体从细到粗
public static let ultraLight: UIFont.Weight
public static let thin: UIFont.Weight
public static let light: UIFont.Weight
public static let regular: UIFont.Weight // 系统默认字体
...2
3
4
5
6
7
8
- 设置文字阴影
label.shadowColor = UIColor.lightGray//设置阴影颜色
label.shadowOffset = CGSizeMake(1, 1)//设置阴影大小2
UITableView
- iOS15之后,UITableView分区会默认留白一部分,可以在
Appdelegate中设置默认不留白
if #available(iOS 15.0, *) {
UITableView.appearance().sectionHeaderTopPadding = 0;
}2
3
- UITableView设置Cell的一些技巧
// 取消选中状态
cell.selectionStyle=UITableViewCellAccessoryNone;
// 取消指定的cell分割线
cell.separatorInset = UIEdgeInsetsMake(0, 0, 0, 120);
// 默认带系统箭头
cell.accessoryType = .disclosureIndicator
// 取消所有cell的分割线
tableview.separatorStyle = UITableViewCellSeparatorStyleNone;
// cell分割线全屏
tableview.separatorInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
// cell分割线颜色
tableview.separatorColor = .red2
3
4
5
6
7
8
9
10
11
12
13
- UITableView 分区设置圆角
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
guard let cell = cell as? TableViewCell else { return }
let radius: CGFloat = 10 //圆角
let rows = tableView.numberOfRows(inSection: indexPath.section) // 分区有多少个cell
let row = indexPath.row
// 如果是纯代码或者xib不想在awakeFromNib里面设置widget的具体大小,这里需要搞成你需要设置的size
let rectBounds = cell.widget.bounds;
// 分区只有一行时默认设置四个圆角角
var maskPath:UIBezierPath = UIBezierPath.init(roundedRect: rectBounds, cornerRadius: radius)
if rows > 1{
if row == 0{ // 当前section不止1行,设置第一行cell的左上角和右上角
maskPath = UIBezierPath.init(roundedRect: rectBounds, byRoundingCorners: [.topLeft,.topRight],cornerRadii: CGSize(width: radius, height: radius))
}else if row == rows-1{ // 设置最后一行cell的的左下角和右下角
maskPath = UIBezierPath.init(roundedRect: rectBounds, byRoundingCorners: [.bottomLeft,.bottomRight], cornerRadii: CGSize(width: radius, height: radius))
}else{ // 当前cell为中间行,不设置圆角
maskPath = UIBezierPath.init(roundedRect: rectBounds, cornerRadius: .zero)
}
}
let maskLayer = CAShapeLayer.init()
maskLayer.frame = rectBounds
maskLayer.path = maskPath.cgPath
cell.widget.layer.mask = maskLayer
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
注意如果cell是由xib进行创建的,需要在awakeFromNib方法中设置圆角控件的具体大小
override func awakeFromNib() {
super.awakeFromNib()
let size = UIScreen.main.bounds.size
widget.frame = CGRect(x: 10, y: 0, width:size.width - 20 , height: 50)
}2
3
4
5
如果是纯代码创建cell,在
init(style: UITableViewCell.CellStyle, reuseIdentifier: String?)方法中获取到的cell宽度也是固定的320,高度是44,需要在willDisplay方法中设置rectBounds的固定大小
- UITbaleViewHeaderView头部地图下拉放大
extension XDSMyController: UIScrollViewDelegate{
func scrollViewDidScroll(_ scrollView: UIScrollView) {
headerView.scrollViewDidScroll(scrollView.contentOffset.y)
}
}
// headerView的方法
func scrollViewDidScroll(_ offset: CGFloat) {
let width = frame.size.width // 图片宽度
if offset < 0 { // 偏移的y值 < 0 ,开始下拉
let totalOffset = self.frame.size.height + abs(offset) // 界面高度 + 下拉高度
let f = totalOffset / self.frame.size.height // 总长度的比例
bgImgView.frame = CGRect(x: -(width * f - width), y: offset, width: width * f, height: totalOffset)
layoutIfNeeded()
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
UITextField
- UITextfield常用属性
let textField = UITextField()
// 设置 样式 (.none 无边框 .line 直线边框 .roundedRect 圆角矩形边框 .bezel 边线+阴影)
textField.borderStyle = UITextBorderStyle.roundedRect
// 设置 提示字
textField.placeholder = "我是 UITextfield"
// 设置 文字超出文本框时自适应大小
textField.adjustsFontSizeToFitWidth = true
// 设置 最小可缩小的字号
textField.minimumFontSize = 13
// 设置 清理按钮 (.never 从不出现 .whileEditing 编辑时出现 .unlessEditing 编辑时不出现 编辑完后出现 .always 一直出现)
textField.clearButtonMode = UITextFieldViewMode.whileEditing
// 设置 键盘样式
textField.keyboardType = UIKeyboardType.default
// 设置密文显示,输入的文字变成 ·
textField.secureTextEntry = true
// 监听文字变化
textField.addTarget(self, action: #selector(valueChange), for: .editingChanged)
// 光标颜色
textField.tintColor = UIColor.white
// 富文本提示文字
let placeholserAttr = [NSAttributedString.Key.foregroundColor : UIColor.init("#5281bb")]
textField.attributedPlaceholder = NSAttributedString(string:"请输入手机号",attributes: placeholserAttr)2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
- UITextfield常用代理
// 输入框询问是否可以编辑 true 可以编辑 false 不能编辑
func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
print("我要开始编辑了...")
return true
}
// 该方法代表输入框已经可以开始编辑 进入编辑状态
func textFieldDidBeginEditing(_ textField: UITextField) {
print("我正在编辑状态中...")
}
// 输入框将要将要结束编辑
func textFieldShouldEndEditing(_ textField: UITextField) -> Bool {
print("我即将编辑结束...")
return true
}
// 输入框结束编辑状态
func textFieldDidEndEditing(_ textField: UITextField) {
print("我已经结束编辑状态...")
}
// 文本框是否可以清除内容
func textFieldShouldClear(_ textField: UITextField) -> Bool {
return true
}
// 输入框按下键盘 return 收回键盘
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
} // 该方法当文本框内容出现变化时 及时获取文本最新内容
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
return true
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
- 禁止粘贴 UITextField禁止粘贴,自定义UITextField重写
-(BOOL)canPerformAction:(SEL)action withSender:(id)sender方法。
class CustomTextField: UITextField {
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
if action == #selector(paste(_:)){
return false
}
// 也可以这样比较方法
// if action == #selector(UIResponderStandardEditActions.paste(_:)){
// return false
// }
return true
}
}2
3
4
5
6
7
8
9
10
11
12
其他一些可以禁止的方法:
cut: // 剪切
copy: // 拷贝
select: // 选择
selectAll: // 全选
paste: // 粘贴
delete: // 删除
_promptForReplace: // Replace...
_transliterateChinese: // 简<=>繁
_showTextStyleOptions: // B/<u>U</u>
_define: // Define
_addShortcut: // Learn...
_accessibilitySpeak: // Speak
_accessibilitySpeakLanguageSelection: // Speak...
_accessibilityPauseSpeaking: // Pause
_share: // 共享...
makeTextWritingDirectionRightToLeft: // 往右缩进
makeTextWritingDirectionLeftToRight: // 往左缩进2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
UIStackView
UIStackView可以自适应内部控件的大小。主要包括了四大属性:axis、alignment、distribution、spacing。
- axis: 布局方向
NSLayoutConstraint.Axis.horizontal //水平
NSLayoutConstraint.Axis.vertical //垂直2
- alignment: 非轴方向子视图的对齐方式
UIStackView.Alignment.fill //子视图填充StackView
UIStackView.Alignment.leading //子视图左对齐(axis为垂直方向而言)
UIStackView.Alignment.top //子视图顶部对齐(axis为水平方向而言)
UIStackView.Alignment.firstBaseline //按照第一个子视图的文字的第一行对齐,同时保证高度最大的子视图底部对齐(只在axis为水平方向有效)
UIStackView.Alignment.center //子视图居中对齐
UIStackView.Alignment.trailing //子视图右对齐(axis为垂直方向而言)
UIStackView.Alignment.bottom //子视图底部对齐(axis为水平方向而言)
UIStackView.Alignment.lastBaseline //按照最后一个子视图的文字的最后一行对齐,同时保证高度最大的子视图顶部对齐(只在axis为水平方向有效)2
3
4
5
6
7
8
- distribution: 设置轴方向上子视图的分布比例(如果axis是水平方向,也即设置子视图的宽度,如果axis是垂直方向,则是设置子视图的高度)
UIStackView.Distribution.fill //填满,不符合则按优先级压缩或拉伸控件
UIStackView.Distribution.fillEqually //子控件等宽(高)
UIStackView.Distribution.fillProportionally //根据每个子视图里面内容的尺寸来进行填充操作
UIStackView.Distribution.equalSpacing //保证间距相等
UIStackView.Distribution.equalCentering //保证每个子视图中心直接的间距相等2
3
4
5
- spacing: 该属性控制子视图之间的间隔大小,在distribution前三个属性值设置的情况下,子视图之间是没有间隔,我们可以通过 spacing 属性显式的设置
- UIStackView 常用的API
open func addArrangedSubview(_ view: UIView) //添加子视图
open func removeArrangedSubview(_ view: UIView) //移除子视图
open func insertArrangedSubview(_ view: UIView, at stackIndex: Int)//插入语子视图
open func setCustomSpacing(_ spacing: CGFloat, after arrangedSubview: UIView)//在view后面设置间距
open func customSpacing(after arrangedSubview: UIView) -> CGFloat //获取view后面设置的间距2
3
4
5
- 示例
let stack = UIStackView.init(frame: CGRect(x: 0, y: 100, width: view.bounds.size.width, height: 100))
stack.axis = .horizontal
stack.alignment = .bottom
stack.distribution = .fillEqually
stack.backgroundColor = .red
view.addSubview(stack)2
3
4
5
6
当UIStackView里面的子控件设置
isHidden = true时,剩下的其他子控件会平分它的位置大小。设置alpha = 0时则会保留它的位置
UIScrollView
- 常用属性
scrollEnabled //禁止滚动
bounces //回弹机制
alwaysBounceHorizontal // 默认值为NO,如果该值设为YES并且bounces也设置为YES,即使contentSize比scrollView的size小,那么也是可以拖动的。
alwaysBounceVertical:默认值为NO,如果该值设为YES // 同上
showsVerticalScrollIndicator //是否显示垂直滚动条
showsHorizontalScrollIndicator // 是否显示水平滚动条2
3
4
5
6
- 使用约束添加UIScrollView
let scrollView = UIScrollView.init()
view.addSubview(scrollView)
scrollView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
let contentView = UIView.init()
scrollView.addSubview(contentView)
contentView.snp.makeConstraints { make in
make.edges.equalToSuperview()
make.width.equalToSuperview() // 这句话必写,决定是纵向还是横向
}
let v1 = UIView.init()
v1.backgroundColor = .yellow
contentView.addSubview(v1)
v1.snp.makeConstraints { make in
make.top.left.right.equalToSuperview()
make.height.equalTo(600)
}
let v2 = UIView.init()
v2.backgroundColor = .blue
contentView.addSubview(v2)
v2.snp.makeConstraints { make in
make.top.equalTo(v1.snp.bottom)
make.bottom.left.right.equalToSuperview()
make.height.equalTo(800)
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
WKWebView
WKWebView需要导入import Webkit框架
- 初始化WKWebView
webView = WKWebView(frame: view.bounds)
webView.scrollView.contentInsetAdjustmentBehavior = .never
webView.isOpaque = false
webView.backgroundColor = UIColor.clear
webView.navigationDelegate = self
webView.uiDelegate = self //监听通过JS调用警告框
webView.scrollView.showsVerticalScrollIndicator = false
webView.scrollView.bounces = false //禁止弹回
view.addSubview(webView)2
3
4
5
6
7
8
9
- WKNavigationDelegate代理方法
extension ViewController: WKNavigationDelegate {
//页面加载完成之后调用
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
weak var weakSelf = self
webView.evaluateJavaScript("Math.max(document.body.scrollHeight, document.body.offsetHeight, document.documentElement.clientHeight, document.documentElement.scrollHeight, document.documentElement.offsetHeight)") { (result, error) in
if result == nil { return }
let height = "\(result!)".floatValue()
weakSelf?.webView.frame.size.height = CGFloat(height)
}
}
}2
3
4
5
6
7
8
9
10
11
12
13
禁止长按弹出提示框需要跟webView进行交互
webView.evaluateJavaScript("document.documentElement.style.webkitTouchCallout='none';",completionHandler: nil)- 获取WkWebView真实高度 通过KVO监听scrollView的的
contentSize属性,当网页加载完成后,通过获取的宽高比,获取真实高度。
override func viewDidLoad() {
super.viewDidLoad()
webView.addObserver(self, forKeyPath: "scrollView.contentSize" , options: [.new, .old], context: nil)
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
// 通过KVO监听
let newValue = change?[NSKeyValueChangeKey.newKey] as? CGSize ?? .zero
print(newValue)
}
extension ViewController: WKNavigationDelegate{
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
// 获取宽度
webView.evaluateJavaScript("document.body.scrollWidth") { body, error in
guard let v = body as? CGFloat else { return }
let ratio = v / UIScreen.main.bounds.size.width
// 获取高度
webView.evaluateJavaScript("document.body.scrollHeight") { [weak self] body1, error1 in
guard let v1 = body1 as? CGFloat else { return }
self?.webH.constant = v1 * ratio
}
}
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
- WKWebView调试App内嵌网页
- 手机端开启Web 检查器:
设置 -> Safari浏览器 -> 高级 -> Web 检查器
- 手机端开启Web 检查器:
- Mac端设置Safari显示开发菜单:
Safari 浏览器 -> 偏好设置 -> 高级 -> 勾选在菜单中显示“开发”设置
- Mac端设置Safari显示开发菜单:
- 手机连接电脑,启动 APP ,打开网页。
- 打开电脑 Safari 浏览器,选择
开发 -> 选择对应设备 ->相关网页。即可看到 H5页面。可通过Safari中断点进行调试,可以查看当前的 HTML 代码,JS 代码,网络情况等
- 打开电脑 Safari 浏览器,选择
UIImage
图片拉伸有三种方法,使用代码或者放到项目中进行拉伸 第一种放在Assets中,选中图片之后可以通过右侧面板Slicing进行拉伸 第二种,使用代码设置保护范围,根据2x、3x设置保护不被拉伸的范围
- (UIImage *)resizableImageWithCapInsets:(UIEdgeInsets)capInsets;
// 使用方式
UIImage *image = [UIImageimageNamed:@"RedButton"];
image = [image resizableImageWithCapInsets:UIEdgeInsetsMake(image.size.height *0.5, image.size.width *0.5, image.size.height *0.5, image.size.width *0.5)];2
3
4
第三种,leftCapWidth: 左边不拉伸的像素,topCapHeight:上边不拉伸的像素
- (UIImage *)stretchableImageWithLeftCapWidth:(NSInteger)leftCapWidth topCapHeight:(NSInteger)topCapHeight__TVOS_PROHIBITED;
// 示例
UIImage *image = [UIImageimageNamed:@"RedButton"];
image = [image stretchableImageWithLeftCapWidth:image.size.width *0.5topCapHeight:image.size.height *0.5];2
3
4
5
拉伸的模式有两种:
UIImageResizingModeTile平铺,默认是平铺UIImageResizingModeStretch拉伸
如果是需要对网络图片进行拉伸,首先将图片下载到本地,对设备来说,网络图片就是1x图,如果你使用过大的图片,拉伸的时候注意图片不要被压缩了。
比如你调试时,使用3x图,大小是258x111,不被拉伸的区域为上55,左120,下55,右70,控件高度是40, 当你下载网络图片之后,会发现图片被压缩,这是因为下载的图片对控件来说是1x图,控件最少的高度需要是图片的高度111才不会被压缩。你可以将图片的大小更改为86x37,这样下载好之后只会拉伸而不会被压缩。
很多时候安卓需要使用大的图片才会显示的更完美,如果需要跟安卓使用同一张图片,可以使用大图,下载之后先等比对图片进行压缩,然后再对其进行拉伸即可
- 保存网络图片到本地相册
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
DispatchQueue.global().async {
guard let url = URL(string: "https://alifei02.cfp.cn/creative/vcg/nowater800/new/VCG41N1437896205.jpg"),
let data = try? Data(contentsOf: url),
let img = UIImage(data: data) else { return }
DispatchQueue.main.async {
UIImageWriteToSavedPhotosAlbum(img, self, #selector(self.save(image:didFinishSavingWithError:contextInfo:)), nil)
}
}
}
@objc func save(image:UIImage, didFinishSavingWithError:NSError?,contextInfo:AnyObject) {
if didFinishSavingWithError != nil {
print("保存失败")
} else {
print("保存成功")
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- 保存图片到本地相册
PHPhotoLibrary.shared().performChanges {
PHAssetChangeRequest.creationRequestForAsset(from: UIImage.init(named: "xxx.png"))
} completionHandler: { success, error in
if success{
print("保存成功")
}else{
print("保存失败")
}
}2
3
4
5
6
7
8
9
- 保存图片到本地相册,并获取路径
///保存图片,并从相册获取保存的照片
func savePhotoAndGetphoto() {
var localID: String!//标识符
PHPhotoLibrary.shared().performChanges({
let result = PHAssetChangeRequest.creationRequestForAsset(from: UIImage())
let assetPlaceholder = result.placeholderForCreatedAsset
//保存标识符
localID = assetPlaceholder?.localIdentifier
}) { (succ, error) in
if succ {
print("保存成功")
//通过标识符获取对应的资源
let assetResult = PHAsset.fetchAssets(withLocalIdentifiers: [localID], options: nil)
let asset = assetResult[0]
let options = PHContentEditingInputRequestOptions()
options.canHandleAdjustmentData = {(adjustmeta: PHAdjustmentData) -> Bool in
return true
}
//获取保存的图片路径
asset.requestContentEditingInput(with: options, completionHandler: { (contentEditingInput: PHContentEditingInput?, info: [AnyHashable : Any]) in
let file = contentEditingInput!.fullSizeImageURL!
print("地址:\(file)")
})
//获取保存的原图
PHImageManager.default().requestImage(for: asset, targetSize: PHImageManagerMaximumSize, contentMode: PHImageContentMode.aspectFit, options: nil, resultHandler: { (image, handle: [AnyHashable : Any]?) in
print("获取原图成功:\(String(describing: image))")
})
//获取保存的缩略图
PHImageManager.default().requestImage(for: asset, targetSize: CGSize.init(width: 100, height: 100), contentMode: PHImageContentMode.aspectFit, options: nil, resultHandler: { (image, handle: [AnyHashable : Any]?) in
print("获取缩略图成功:\(String(describing: image))")
})
} else {
print("保存失败-->\(error!.localizedDescription)")
}
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
UITabBar
// 通过KVO的方式使用自定义TabBar
class TabBarController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
setValue(TabBar(), forKeyPath: "tabBar")
}
}
class TabBar: UITabBar {
override func layoutSubviews() {
super.layoutSubviews()
for button in subviews where button is UIControl{
var frame = button.frame
frame.origin.y = -2
button.frame = frame
}
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
UISlider
// 设置滑动的颜色和默认颜色
slider.setMaximumTrackImage(UIColor.gray.toImage(1), for: .normal)
slider.setMinimumTrackImage(UIColor.themeColor.toImage(1), for: .normal)
// 设置最大最小value
slider.minimumValue = 0
slider.maximumValue = 5
// 设置value
slider.value = 5
// 滑动时调用
slider.addTarget(self, action: #selector(changeValue), for: .valueChanged)
// 抬起时调用
slider.addTarget(self, action: #selector(touchUp), for: .touchUpInside)
// 设置变量
@objc func touchUp(){
slider.value = roundf(slider.value);
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
如果需要在UISlider上进行分段,直接用slider.width / count,在滑动的时候会发现中心点不对,这是因为UISlider的thumbImage在两端的时候不是处于中心位置。参考自定义等分段落式滑块
private var lineViews: [UIView] = Array()
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
_ = lineViews.map { item in
item.removeFromSuperview()
}
let count = maxCount
let width = (slider.width - 10) / CGFloat(count)
for i in 0..<count + 1{
let v = UIView.init()
let x = slider.x + ratioW(5) + CGFloat(i) * width
let y = slider.y + slider.height + 2
v.frame = CGRect(x:x , y: y, width: 1, height: 3)
v.backgroundColor = UIColor.themeColor
moneyView.insertSubview(v, belowSubview: slider)
lineViews.append(v)
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
UIPopoverPresentationController
UIPopoverPresentationController 创造弹出视图,比如带箭头的视图。
let vc = XDSPopController()
// 弹出状态
vc.modalPresentationStyle = UIModalPresentationStyle.popover;
let pover = vc.popoverPresentationController;
// 显示视图
pover?.sourceView = btn
// 显示位置,位置根据sourceView来的
pover?.sourceRect = btn.bounds
pover?.delegate = self
// 箭头方向
pover?.permittedArrowDirections = .up
present(vc, animated: true, completion: nil)
extension XDSMainController: UIPopoverPresentationControllerDelegate {
func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
return UIModalPresentationStyle.none
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
序列化和反序列化
Swift4 中引入了 Codable 协议,我们只需要让需要序列化的对象符合 Codable 协议即可,不用再写任何其他的代码
class Person: NSObject, HandyJSON,Codable{
var age: String?
var name: String?
required override init() {}
}
class MUserMode: NSObject, HandyJSON,Codable {
var token: String?
var account: String?//注册时填写的手机号
var list: [String]?//用户id
var person: Person?
required override init() {}
static func filePath () -> String?{
if let basePath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first{
let className = String(describing: type(of: self))
return basePath.appending("/\(className)")
}
return nil
}
func archived(){
let selfClass = type(of: self)
guard let filePath = selfClass.filePath() else { return }
let fileUrl = URL(fileURLWithPath: filePath)
let data = try? JSONEncoder().encode(self)
try? data?.write(to: fileUrl, options: .atomic)
}
static func unarchived() -> Self?{
guard let filePath = self.filePath() else { return nil }
let fileUrl = URL(fileURLWithPath: filePath)
guard let data = try? Data.init(contentsOf: fileUrl) else { return nil }
let decodedData = try? JSONDecoder().decode(self, from: data)
return decodedData
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
在项目中使用
override func viewDidLoad() {
super.viewDidLoad()
let dict : Dictionary<String,Any> = ["token":"用户ID","account":"头像地址","list":["1","2","3"],"person":["age":"10","name":"name123"]]
let mode = MUserMode.deserialize(from: dict)
mode?.archived()
let m = MUserMode.unarchived()2
3
4
5
6
7
8
UIAlertController
- 使用 UIAlertController
let alert = UIAlertController.init(title: "标题", message: "消息", preferredStyle: .alert)
let cancel = UIAlertAction(title: "取消", style: UIAlertAction.Style.cancel)
let ok = UIAlertAction(title: "确定", style: UIAlertAction.Style.default)
// 通过 KVC 改变字体颜色
ok.setValue(UIColor.black, forKey: "titleTextColor")
// 添加TextField
alert.addTextField { textField in }
alert.addAction(cancel)
alert.addAction(ok)
present(alert, animated: true)2
3
4
5
6
7
8
9
10
11
12
13
- 改变标题、及提示信息的字体
// 改变标题文字颜色
let titleAttr = NSAttributedString.init(string: "标题",attributes: [
NSAttributedString.Key.font: UIFont.systemFont(ofSize: 20),
NSAttributedString.Key.foregroundColor: UIColor.red
])
alert.setValue(titleAttr, forKey: "attributedTitle")
// 改变消息文字颜色
let msgAttr = NSAttributedString.init(string: "测试可变消息",attributes: [.font: UIFont.systemFont(ofSize: 18),.foregroundColor: UIColor.blue ])
alert.setValue(msgAttr, forKey: "attributedMessage")2
3
4
5
6
7
8
9
10
- iPad调用UIAlertController,需要在
present方法前添加以下代码进行适配
if UIDevice.current.userInterfaceIdiom == .pad{
let pop = alert.popoverPresentationController
pop?.sourceView = view
pop?.permittedArrowDirections = .up
pop?.sourceRect = view.bounds
}
present(alert, animated: true)2
3
4
5
6
7