给UILabel设置文字渐变色
// requiredSize 为 frame.size
label.textColor = .init(patternImage: .gradient(size: label.requiredSize,
startColor: .hex("#FFECBA"),
endColor: .hex("#FFBE8D"),
startPoint: .init(x: 0.5, y: 0),
endPoint: .init(x: 0.5, y: 1)))
extension UIImage {
static func gradient(size: CGSize,
startColor: UIColor, endColor: UIColor,
startPoint: CGPoint, endPoint: CGPoint) -> UIImage {
let colorspace = CGColorSpaceCreateDeviceRGB()
let gradientLocations: [CGFloat] = [0.0, 1.0]
let colors: CFArray = [startColor.cgColor, endColor.cgColor] as CFArray
let gradient = CGGradient(colorsSpace: colorspace, colors: colors, locations: gradientLocations)
let format = UIGraphicsImageRendererFormat()
format.scale = UIScreen.main.scale
let render = UIGraphicsImageRenderer(bounds: .init(origin: .zero, size: size), format: format)
let image = render.image { context in
context.cgContext.drawLinearGradient(gradient!,
start: CGPoint(x: size.width * startPoint.x, y: size.height * startPoint.y),
end: CGPoint(x: size.width * endPoint.x, y: size.height * endPoint.y),
options: .drawsAfterEndLocation)
}
return image
}
}
给UIView添加渐变圆角
extension UIView {
/// 添加渐变圆角(基于frame)
func addGradientBorder(opacity: Float, colors: [UIColor], startPoint: CGPoint, endPoint: CGPoint, borderWidth: CGFloat, cornerRadius: CGFloat) {
let frame = self.bounds
let gradientLayer = CAGradientLayer()
gradientLayer.frame = frame
gradientLayer.opacity = opacity
gradientLayer.colors = colors.map({ $0.cgColor })
gradientLayer.startPoint = startPoint
gradientLayer.endPoint = endPoint
let borderInset = borderWidth / 2.0
let maskFrame = CGRect(x: borderInset, y: borderInset, width: frame.width - borderInset, height: frame.height - borderInset)
let maskLayer = CAShapeLayer()
maskLayer.lineWidth = borderWidth
maskLayer.path = UIBezierPath(roundedRect: maskFrame, cornerRadius: cornerRadius).cgPath
maskLayer.fillColor = UIColor.clear.cgColor
maskLayer.strokeColor = UIColor.black.cgColor
gradientLayer.mask = maskLayer
self.layer.addSublayer(gradientLayer)
}
}
给UIView添加自定义圆角+阴影
// ⚠️ 阴影在下,圆角在上,否则阴影会被覆盖,导致无效
class CornerShadowView: UIView {
lazy var cornerView = UIView(backgroundColor: .white)
override init(frame: CGRect) {
super.init(frame: frame)
ez_size = .init(width: App.width, height: 120.wScale)
ez_addShadow(ofColor: .black, radius: 3.5, offset: .init(width: 1.5, height: 2), opacity: 0.1)
addSubview(cornerView)
cornerView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
layoutIfNeeded() // 如果不这么做,第一次显示不出来。。。神奇
cornerView.ez_roundCorners([.topLeft, .topRight], radius: 18.wScale)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
/// 添加阴影
/// - Parameters:
/// - color: 颜色
/// - radius: 半径
/// - offset: 偏移量
/// - opacity: 透明度
func ez_addShadow(ofColor color: UIColor, radius: CGFloat, offset: CGSize, opacity: Float) {
layer.shadowColor = color.cgColor
layer.shadowOffset = offset
layer.shadowRadius = radius
layer.shadowOpacity = opacity
layer.masksToBounds = false
}
/// 添加自定义角落圆角(基于frame)
/// - Parameters:
/// - corners: [UIRectCorner]
/// - radius: 半径
func ez_roundCorners(_ corners: UIRectCorner, radius: CGFloat) {
let maskPath = UIBezierPath(
roundedRect: bounds,
byRoundingCorners: corners,
cornerRadii: CGSize(width: radius, height: radius))
let shape = CAShapeLayer()
shape.path = maskPath.cgPath
layer.mask = shape
}
}
关于UIStackView的一个小知识点
把UIStackView中某个视图hidden后,UIStackView的布局会进行更新,只展示没有hidden的视图, 例如,你有5个视图平等分显示,设置某个视图hidden之后,就会变成4个视图平等分了。 如果hidden某个视图后,不想更改其他视图布局,那么可以设置alpha = 0
如何在键盘光标处插入、删除字符?
// 系统输入控件都遵守了UITextInput协议
class UITextView : UIScrollView, UITextInput {}
class UITextField : UIControl, UITextInput {}
@MainActor public protocol UIKeyInput : UITextInputTraits {
/// 是否有文字?用于判断删除、完成键是否可用。
var hasText: Bool { get }
/// 在光标处插入字符串
func insertText(_ text: String)
/// 从光标处删除字符串
func deleteBackward()
}
关于UIView通过AutoLayout获取frame的时机
// 假定当前View里有一个hStackView,然后titleLabel又在stack中
override func layoutSubviews() { // 父视图会先调用
super.layoutSubviews()
logDebug(#function, titleLabel) // 此时frame == .zero
hStackView.layoutIfNeeded() // 当label的父视图布局完后
logDebug(#function, titleLabel) // label.frame != .zero
}
通过代码调用主动进入后台
let suspend = #selector(NSXPCConnection.suspend)
if UIApplication.shared.responds(to: suspend) {
UIApplication.shared.perform(suspend)
}
等宽字体可解决倒计时Label跳动的问题
/// 样式更接近系统默认字体
@available(iOS 9.0, *)
open class func monospacedDigitSystemFont(ofSize fontSize: CGFloat, weight: UIFont.Weight) -> UIFont
/// 样式差异较大,谨慎使用
@available(iOS 13.0, *)
open class func monospacedSystemFont(ofSize fontSize: CGFloat, weight: UIFont.Weight) -> UIFont