Skills
让扩展增加命名空间
比如以下,就可以使用 UIColor.designKit.primary 这种方式来使用了
public extension UIColor {
static let designKit = DesignKitPalette.self
enum DesignKitPalette {
public static let primary: UIColor = dynamicColor(light: UIColor(hex: 0x0770e3), dark: UIColor(hex: 0x6d9feb))
public static let background: UIColor = dynamicColor(light: .white, dark: .black)
...
public static let quaternaryText: UIColor = dynamicColor(light: UIColor(hex: 0xb2b2bf), dark: UIColor(hex: 0x8E8E93))
static private func dynamicColor(light: UIColor, dark: UIColor) -> UIColor {
return UIColor { $0.userInterfaceStyle == .dark ? dark : light }
}
}
}
hex color转UIColor
public extension UIColor {
convenience init(hex: Int) {
let components = (
R: CGFloat((hex >> 16) & 0xff) / 255,
G: CGFloat((hex >> 08) & 0xff) / 255,
B: CGFloat((hex >> 00) & 0xff) / 255
)
self.init(red: components.R, green: components.G, blue: components.B, alpha: 1)
}
}
数组, Array
reduce的另一种写法
let array = [1,2,3,4,5]
array.reduce(0, combine: +) // 15
array.reduce(1, combine: *) // 120
// 等同于
array.reduce(0) { $0 + $1}
array.reduce(1) { $0 * $1}
for-in和for-each的区别在于for-in是应用于enumerate的,即遍历的时候可以带一个索引:
let scores = [84, 76, 91, 62 ,80]
for (index, score) in scores.enumerate() {
print("index:\(index), score: \(score)")
// index:0, score: 84
// index:1, score: 76
// index:2, score: 91
// index:3, score: 62
// index:4, score: 80
}
// 但是for-in也可以:
scores.enumerate().forEach { print("index:\($0.0), score: \(0.1)") }
取最大值最小值
let scores = [84, 76, 91, 62 ,80]
scores.minElement() // 62
scores.maxElement() // 91
// minElement如果你不使用and maxElement,你仍然可以写:
scores.sort(<).first // 62
scores.sort(>).first // 91
flatMap降维数组时,也可以写成:
let array: [[Int]] = [[1,41,6],[],[20,451,7]]
let flatArray = array.map {$0} .flatten() // [1,41,6,20,451,7]
Navigation Bar
导航条和状态栏固态颜色,并且一致
let dic: [NSAttributedString.Key: Any] = [.foregroundColor: UIColor.white, .font: UIFont.OverpassRegular(size: 18.0) as Any]
self.navigationController?.navigationBar.barStyle = .black
if #available(iOS 15, *) {
// setup navBar.....无效?
// UINavigationBar.appearance().barTintColor = .green
// UINavigationBar.appearance().tintColor = .red
// UINavigationBar.appearance().titleTextAttributes = dic
// UINavigationBar.appearance().isTranslucent = false
// UINavigationBar.appearance().isOpaque = true
let appearance = UINavigationBarAppearance()
appearance.titleTextAttributes = dic
appearance.configureWithOpaqueBackground()
appearance.backgroundColor = .theme.bgBlack
appearance.backgroundImage = UIImage()
appearance.shadowColor = nil; //阴影
// appearance.backgroundEffect = UIBlurEffect(style: .dark)
self.navigationController?.navigationBar.standardAppearance = appearance;
self.navigationController?.navigationBar.scrollEdgeAppearance = self.navigationController?.navigationBar.standardAppearance
} else {
navigationController?.navigationBar.barTintColor = .theme.bgBlack
navigationController?.navigationBar.titleTextAttributes = dic
navigationController?.navigationBar.setBackgroundImage(UIImage(), for: UIBarMetrics.default)
navigationController?.navigationBar.shadowImage = UIImage()
}
截图
func capture() {
let render = UIGraphicsImageRenderer(bounds: canvas.bounds)
let image = render.image { context in
canvas.drawHierarchy(in: canvas.bounds, afterScreenUpdates: true)
}
if let data = image.pngData() {
// let path = NSTemporaryDirectory() + String(Date().timeIntervalSince1970) + ".png"
// print("file path: \(path)")
// try? data.write(to: URL(fileURLWithPath: path))
let path = getPath(for: .cachesDirectory).appendingPathComponent(String(Date().timeIntervalSince1970) + ".png")
print("save image to: \(path.absoluteString)")
try? data.write(to: path)
}
}
// 其中:
public func getPath(for directory: FileManager.SearchPathDirectory) -> URL {
let paths = FileManager.default.urls(for: directory, in: .userDomainMask)
return paths[0]
}
AttributedString
UIButton Configuration
两个知识点一起演示
var configuration = UIButton.Configuration.filled()
var container = AttributeContainer()
container.font = UIFont.boldSystemFont(ofSize: 20)
// 1
configuration.attributedTitle = AttributedString("Title", attributes: container)
var container2 = AttributeContainer()
container2.foregroundColor = UIColor.white.withAlphaComponent(0.5)
// 2
configuration.attributedSubtitle = AttributedString("Subtitle", attributes: container2)
configuration.image = UIImage(systemName: "swift")
configuration.titleAlignment = .leading
// 3
configuration.preferredSymbolConfigurationForImage = UIImage.SymbolConfiguration(pointSize: 30)
configuration.imagePadding = 10
let button = UIButton(configuration: configuration, primaryAction: nil)
configuration.imagePlacement = .top
let button2 = UIButton(configuration: configuration, primaryAction: nil)
button configuration有:plain, gray, tinted, and filled.
// 1,使用默认值
let plain = UIButton(configuration: .plain(), primaryAction: nil)
plain.setTitle("Plain", for: .normal)
// 2,修改属性
var configuration = UIButton.Configuration.gray() // 1
configuration.cornerStyle = .capsule // 2
configuration.baseForegroundColor = UIColor.systemPink
configuration.buttonSize = .large
configuration.title = "Gray Capsule"
let button = UIButton(configuration: configuration, primaryAction: nil) // 3
// 3, title, subtitle, image
var configuration = UIButton.Configuration.filled()
configuration.title = "Title"
configuration.subtitle = "Subtitle"
configuration.image = UIImage(systemName: "swift")
let button = UIButton(configuration: configuration, primaryAction: nil) // 1
configuration.titleAlignment = .center
let button2 = UIButton(configuration: configuration, primaryAction: nil)
configuration.titleAlignment = .trailing
let button3 = UIButton(configuration: configuration, primaryAction: nil)
// 4, 正副标题间是titlePadding,图片文字间是imagePadding,内边距是contentInsets
// 5, 图片有imagePlacement: .leading, .top, .trailing, .bottom
var configuration = UIButton.Configuration.filled()
configuration.title = "Title"
configuration.subtitle = "Subtitle"
configuration.image = UIImage(systemName: "swift")
configuration.titlePadding = 10
configuration.imagePadding = 10
configuration.contentInsets = NSDirectionalEdgeInsets(top: 2, leading: 20, bottom: 2, trailing: 20)
// 6, 更多自定义看第一个例子
// 7, button size
configuration.buttonSize = .small // .large, .medium, .mini
// 8, 圆角
configuration.background.cornerRadius = 2
configuration.cornerStyle = .small // .capsule, .large, .medium, // .fixed, .dynamic
// 9, 颜色
var configuration = UIButton.Configuration.filled()
configuration.baseBackgroundColor = UIColor.systemIndigo // 1
configuration.baseForegroundColor = UIColor.systemPink // 2
enum, case
让enum具有CaseIterable特性:
extension Suit: CaseIterable {} // 如果是别人的enum
// 如果是自己的,直接加声明
enum Suit: String, CaseIterable {
case spades = "♠";
case hearts = "♥";
case diamonds = "♦";
case clubs = "♣" }
// 使用
Suit.allCases.forEach {
print($0.rawValue)
}
String
替换
// 就是NSRange不能直接用的意思
after.replacingCharacters(in: Range(range, in: after)!, with: string)
截屏、录屏和监测
UIGraphicsBeginImageContext(view.frame.size)
view.layer.renderInContext(UIGraphicsGetCurrentContext())
// 或
view.drawHierarchy(in: view.bounds, afterScreenUpdates: false)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
// 分享
var imagesToShare = [AnyObject]()
imagesToShare.append(image)
let activityViewController = UIActivityViewController(activityItems: imagesToShare , applicationActivities: nil)
activityViewController.popoverPresentationController?.sourceView = self.view
presentViewController(activityViewController, animated: true, completion: nil)
NotificationCenter.default.addObserver(self, selector: #selector(screenshotTaken), name: UIApplication.userDidTakeScreenshotNotification, object: nil)
// or
NotificationCenter.default.addObserver(forName: UIApplication.userDidTakeScreenshotNotification, object: nil, queue: OperationQueue.main) { notification in
print("Screenshot taken!")
}
录屏则是这个通知:capturedDidChangeNotification
@objc func screenCaptureDidChange() {
debugPrint("screenCaptureDidChange.. isCapturing: \(UIScreen.main.isCaptured)")
if !UIScreen.main.isCaptured {
//TODO: They stopped capturing..
} else {
//TODO: They started capturing..
debugPrint("screenCaptureDidChange - is recording screen")
}
}
一个例子:ios-detect-screen-capture-and-screen-recording.swift
删除最后一张照片(通常就是截屏)
func didTakeScreenshot() {
self.perform(#selector(deleteAppScreenShot), with: nil, afterDelay: 1, inModes: [])
}
@objc func deleteAppScreenShot() {
let fetchOptions = PHFetchOptions()
fetchOptions.sortDescriptors?[0] = Foundation.NSSortDescriptor(key: "creationDate", ascending: true)
let fetchResult = PHAsset.fetchAssets(with: PHAssetMediaType.image, options: fetchOptions)
guard let lastAsset = fetchResult.lastObject else { return }
PHPhotoLibrary.shared().performChanges {
PHAssetChangeRequest.deleteAssets([lastAsset] as NSFastEnumeration)
} completionHandler: { (success, errorMessage) in
if !success, let errorMessage = errorMessage {
print(errorMessage.localizedDescription)
}
}
}
app switcher时隐藏页面内容
- 其实就是加一个视图(窗口),在应用程序退到后台时显示出来
- iPad需要多窗口支持 [Hide Sensitive Information in the iOS App Switcher Snapshot Imagehttps://hacknicity.medium.com/hide-sensitive-information-in-the-ios-app-switcher-snapshot-image-25ddc9b8ef5f]
func sceneWillEnterForeground(_ scene: UIScene) {
// Called as the scene transitions from the background to the foreground.
// Use this method to undo the changes made on entering the background.
hidePrivacyProtectionWindow()
}
func sceneDidEnterBackground(_ scene: UIScene) {
// Called as the scene transitions from the foreground to the background.
// Use this method to save data, release shared resources, and store enough scene-specific state information
// to restore the scene back to its current state.
showPrivacyProtectionWindow()
}
// MARK: Privacy Protection
private var privacyProtectionWindow: UIWindow?
private func showPrivacyProtectionWindow() {
guard let windowScene = self.window?.windowScene else {
return
}
privacyProtectionWindow = UIWindow(windowScene: windowScene)
privacyProtectionWindow?.rootViewController = PrivacyProtectionViewController()
privacyProtectionWindow?.windowLevel = .alert + 1
privacyProtectionWindow?.makeKeyAndVisible()
}
private func hidePrivacyProtectionWindow() {
privacyProtectionWindow?.isHidden = true
privacyProtectionWindow = nil
}
调用协议方法
检查一个类是否响应了某个协议,然后调用其协议方法:
// 关键点是:as any,比普通的as成具体类多了一个any
if let dv = dateView as? any resetable {
dv.reset()
}
思考:为什么不直接定义为:
var delegate: someProtocol? 因为你定义一个类的时候只能写一个类型,如果它既要继承某个父类,又要实现某个协议,那么只能在代码里测试了
async, await
let frozenProducts = await fetchProducts(fromCategory: .frozen)
let meatProducts = await fetchProducts(fromCategory: .meat)
let vegetablesProducts = await fetchProducts(fromCategory: .vegetals)
如果有多个异步请求,这么写的话,这三个请求其实是阻断的,只是语法糖简单了一些
要真的并发请求,可以用async let,或者说,能显著减少await的使用,当你需要await一堆前提条件的时候(当然你就多了一堆async前缀...)
// 下面的例子不严谨,在其它教程里,async let后面的请求是不需要接await的
async let frozenProducts = await fetchProducts(fromCategory: .frozen)
async let meatProducts = await fetchProducts(fromCategory: .meat)
async let vegetablesProducts = await fetchProducts(fromCategory: .vegetables)
let products = await [frozenProducts, meatProducts, vegetablesProducts]
read line by line use async/await
The main thing they added that enables this, is AsyncSequence. AsyncSequence`` is like Sequence, but its `Iterator.next method is async throws. (即一个async的序列)
let (bytes, response) = try await URLSession.shared.bytes(from: URL(string: "file://...")!)
for try await line in bytes.lines {
// do something...
}
至于为什么用
URLSession而不是另一个基于文件的FileHandle.AsyncBytes.lines,见stackoverflow的讨论
在同步代码块里引用async代码, Task,以及 TaskGroup
- 比如UIKit里的方法,都是不能标记
async的,包到Task { }里即可 - 既然讲到了
Task,顺便介绍下TaskGroup
extension MusicLibrary {
mutating func addAlbums(barcodes: [String]) async {
await withTaskGroup(of: Album.self) { group in // 包一个TaskGroup,指明子方法的返回值是Album类
for barcode in barcodes {
group.addTask { // 添加到group的时机
return await getAlbumFromBarcode(barcode: barcode)
}
}
// 这里是for-await, 那应该就有waitAll吧
for await album in group {
self.albums.append(album)
}
}
}
}
改造closure风格的代码为async的:
原代码:
func fetchData(from url: URL, _ completionHandler: @escaping (Result<Data, Error>) -> Void) {
let dataTask = URLSession.shared.dataTask(with: url) { data, _, error in
if let error = error {
completionHandler(.failure(error))
} else if let data = data {
completionHandler(.success(data))
}
}
dataTask.resume()
}
包一层,调用原代码
// 也可以改为别的名字
func fetchData(from url: URL) async throws -> Data {
return try await withCheckedThrowingContinuation { continuation in
// 原参调用原方法,在回调里resume为return或throw
fetchData(from: url) { result in
switch result {
case .success(let data):
continuation.resume(returning: data)
case .failure(let error):
continuation.resume(throwing: error as! Never)
}
}
}
}
- 如果原方法不抛异常,则改用
withCheckedContinuation resume方法只能调用一次
Codable
import Foundation
struct Person: Codable {
let firstName: String
let lastName: String
let age: Int
}
let person = Person(firstName: "John", lastName: "Doe", age: 30)
// Encoding to JSON
let encoder = JSONEncoder()
let data = try encoder.encode(person)
// Decoding from JSON
let decoder = JSONDecoder()
let decodedPerson = try decoder.decode(Person.self, from: data)
print(decodedPerson)
// Output: Person(firstName: "John", lastName: "Doe", age: 30)
Bundle
找文件/资源所在的bundle
// 用任何class初始一个Bundle对象
class ModuleResourceClass: NSObject {
static var bundle: Bundle {
get {
Bundle(for: Self.self)
}
}
}
// 使用,用bundle对象可以得到任意资源地址(只要有相应的名称)
public func BundleImage(_ imageName: String, moduleName: String) -> UIImage {
let url = ModuleResourceClass.bundle.url(forResource: moduleName, withExtension: "bundle")
guard let u = url else { return UIImage() }
let bundle = Bundle(url: u)
guard let b = bundle else { return UIImage() }
let image = UIImage(named: imageName, in: b, compatibleWith: nil)
guard let img = image else { return UIImage() }
return img
}
反色显示图片
view.layer.compositingFilter = "exclusionBlendMode"
Xcode error: Framework not found
If you use CocoaPods try to run:
pod deintegrate
pod update
Also use .xcworkspace instead of .xcodeproj
截图和保存图片的区别
func snapshotView(afterScreenUpdates afterUpdates: Bool) -> UIView?
截图是输出一个UIView,而保存图片是存成一张UIImage:
UIGraphicsBeginImageContextWithOptions(self.webView.frame.size, NO, [UIScreen mainScreen].scale);
// [self.webView.layer renderInContext:UIGraphicsGetCurrentContext()];
[self.webView drawViewHierarchyInRect:self.webView.bounds afterScreenUpdates:YES];
UIImage *signatureImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
一些反射,运行时方法调用
let sign = "track:"
let obj: AnyClass? = objc_lookUpClass("LCAnalytics")
let selector = sel_getUid(sign)
if let myClass = obj as? NSObjectProtocol, myClass.responds(to: selector) {
myClass.perform(selector, with: ["event": eventId, "pageName": pageName, "prop": props])
}
// let isa = Bundle.main.classNamed("LCAnalytics")
// let isa = NSClassFromString("LCAnalytics")
// let isa = objc_lookUpClass("LCAnalytics")
// let isa = object_getClass("LCAnalytics")
// let selector = sel_getUid("methodName")
// let method = class_getInstanceMethod(isa, selector)
// guard let selector = class_getClassMethod(obj, method)
// else { return }
// let methodIMP :IMP = method_getImplementation(selector)
//// let result :NSObject =
// guard let method = class_getClassMethod(obj, selector) else { return }
// let imp = method_getImplementation(method)
//
// typealias MyMethodImp = @convention(c) (AnyObject, Selector) -> Void
// let myMethod = unsafeBitCast(imp, to: MyMethodImp.self)
// myMethod(self, selector)
修改UITabBar背景色
let backgroundColor = UIColor.black.withAlphaComponent(0.85)
let selectedItemTextColor = UIColor.brand.yellow;
let unselectedItemTextColor = UIColor.white.withAlphaComponent(0.8)
// 文字偏移在vc.tabBarItem里设置失效,可以来appearance设置
let vOffset = UIOffset(horizontal: 0.0, vertical: 8.0)
if #available(iOS 13, *) {
let tabBarAppearance = UITabBarAppearance()
tabBarAppearance.backgroundColor = backgroundColor
tabBarAppearance.stackedLayoutAppearance.selected.titleTextAttributes = [.foregroundColor: selectedItemTextColor]
tabBarAppearance.stackedLayoutAppearance.normal.titleTextAttributes = [.foregroundColor: unselectedItemTextColor]
tabBarAppearance.stackedLayoutAppearance.normal.titlePositionAdjustment = vOffset
tabBarAppearance.stackedLayoutAppearance.selected.titlePositionAdjustment = vOffset
tabBar.standardAppearance = tabBarAppearance
if #available(iOS 15.0, *) {
tabBar.scrollEdgeAppearance = tabBarAppearance
} else {
// Fallback on earlier versions
}
} else {
let appearance = UITabBarItem.appearance()
appearance.setTitleTextAttributes([.foregroundColor: selectedItemTextColor], for: .selected)
appearance.setTitleTextAttributes([.foregroundColor: unselectedItemTextColor], for: .normal)
appearance.titlePositionAdjustment = vOffset
tabBar.barTintColor = backgroundColor
}
如果直接改页面背景色,则如下用法,但是要注意,tabviewcontroller的子页面需要有自己的背景色
self.view.backgroundColor = .black.withAlphaComponent(0.85)
self.tabBar.barTintColor = .white.withAlphaComponent(0.8)
self.tabBar.tintColor = .brand.yellow;
AsyncStream 和 Atomics
- 把一个普通的遍历变成async的
- 多线程里正确使用cancel
import Photos
import Atomics
...
let asyncStream = AsyncStream<PHAsset> { continuation in
// Use ManagedAtomic to wrap a bool
let cancelled = ManagedAtomic<Bool>(false)
continuation.onTermination = { _ in
cancelled.store(true, ordering: .relaxed)
}
assets.enumerateObjects { (asset, _, stop) in
if cancelled.load(ordering: .relaxed) {
stop.pointee = true
} else {
continuation.yield(asset)
}
}
continuation.finish()
}
// Now iterate over the asyncStream awaiting each element
for await asset in asyncStream {
...
}
扩展Kingfisher
final class ImageManager {
static func setImage(imageView: UIImageView, filename: String, isAuth: Bool, completion: ((CGSize) -> ())? = nil) {
guard let url = URL(string: API.host + API.Path.storageDownload(filename: filename).url) else {
return
}
var options: KingfisherOptionsInfo = []
if filename.hasSuffix(".svg") {
let processor = SVGImgProcessor()
options.append(.processor(processor))
}
if isAuth {
let modifier = AnyModifier { request in
var requestTemp = request
if let token = try? KeychainManager.shared.authToken.value() {
requestTemp.setValue("Basic \(token)", forHTTPHeaderField: "Authorization")
}
return requestTemp
}
options.append(.requestModifier(modifier))
}
imageView.kf.setImage(with: url, options: options, completionHandler: { result in
switch result {
case .success(let value):
let size = value.image.size
completion?(size)
case .failure(let error):
print(error.localizedDescription)
completion?(.zero)
}
})
}
}
KVO
class PersonObserver {
var kvoToken: NSKeyValueObservation?
func observe(person: Person) {
kvoToken = person.observe(\.age, options: .new) { (person, change) in
guard let age = change.new else { return }
print("New age is: \(age)")
}
}
deinit {
kvoToken?.invalidate()
}
}
强制横屏,竖屏,自动旋转等
//运行页面随设备转动
public override var shouldAutorotate : Bool {
return false
}
public override func viewWillAppear(_ animated: Bool) {
NotificationCenter.default.post(name: .landscapeRightNotification, object: nil, userInfo: ["type": "1"])
// 强制横屏
// let value = UIInterfaceOrientation.landscapeRight.rawValue
// UIDevice.current.setValue(value, forKey: "orientation")
super.viewWillAppear(animated)
}
public override func viewWillDisappear(_ animated: Bool) {
// 强制竖屏
NotificationCenter.default.post(name: .landscapeRightNotification, object: nil, userInfo: ["type": "0"])
// let value = UIInterfaceOrientation.portrait.rawValue
// UIDevice.current.setValue(value, forKey: "orientation")
super.viewWillDisappear(animated)
}
Result
func fetchUnreadCount1(from urlString: String, completionHandler: @escaping (Result<Int, NetworkError>) -> Void) {
guard let url = URL(string: urlString) else {
completionHandler(.failure(.badURL)) // 回调错误
return
}
// complicated networking code here
print("Fetching \(url.absoluteString)...")
completionHandler(.success(5)) // 回调成功
}
// 使用
fetchUnreadCount1(from: "https://www.hackingwithswift.com") { result in
switch result {
case .success(let count):
print("\(count) unread messages.")
case .failure(let error):
print(error.localizedDescription)
}
}
它增强了下面这种可选返回值的用法:
func fetchUnreadCount2(from urlString: String, completionHandler: @escaping (Int?, NetworkError?) -> Void) {
guard let url = URL(string: urlString) else {
completionHandler(nil, .badURL)
return
}
print("Fetching \(url.absoluteString)...")
completionHandler(5, nil)
}
或使用try-catch的方法:
func fetchUnreadCount3(from urlString: String, completionHandler: @escaping (() throws -> Int) -> Void) {
guard let url = URL(string: urlString) else {
completionHandler { throw NetworkError.badURL }
return
}
print("Fetching \(url.absoluteString)...")
completionHandler { return 5 }
}
fetchUnreadCount3(from: "https://www.hackingwithswift.com") { resultFunction in
do {
let count = try resultFunction()
print("\(count) unread messages.")
} catch {
print(error.localizedDescription)
}
}
显然用Result要简洁多了。其它特性:
get方法可以获取内部值,并且在失败时抛个异常
fetchUnreadCount1(from: "https://www.hackingwithswift.com") { result in
if let count = try? result.get() {
print("\(count) unread messages.")
}
}
- 不穷举而是直接用枚举比较:
fetchUnreadCount1(from: "https://www.hackingwithswift.com") { result in
if case .success(let count) = result {
print("\(count) unread messages.")
}
}
- Result的构造函数支持一个throw clsoure
let result = Result { try String(contentsOfFile: someFile) }
map(),flatMap(),mapError(), andflatMapError()以map为例,假定有两个返回Result的方法,第一个方法是:
let result1 = generateRandomNumber(maximum: 11)
let stringNumber = result1.map { "The random number is: \($0)." }
这样可以直接把结果map成另一个Result,假如我要用这个结果调第二个返回Result的方法:
let result2 = generateRandomNumber(maximum: 10)
let mapResult = result2.map { calculateFactors(for: $0) }
这样也可以,但calculateFactors方法返回的是一个Result,显然map成了一个Result的Result: Result<Result<Int, Error>, Error>,这时候用flatMap就行了
let flatMapResult = result2.flatMap { calculateFactors(for: $0) }
这个时候返回已经是Result<Int, Error>了,你想改成例一中的字段串,那还得继续跟一个map
背景图重复
imageView.image = image.resizableImage(withCapInsets: .zero, resizingMode: .tile)
常量
swift常量可以这么处理
struct Key {
struct NotificationKey {
static let Welcome = "kWelcomeNotif"
}
struct Path {
static let Documents = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as String
static let Tmp = NSTemporaryDirectory()
}
}
使用的时候调用
print(Key.NotificationKey.Welcome)
print(Key.Path.Documents)
print(Key.Path.Tmp)
使用path, mask来高亮某个区域
比如框出摄像界面里的中间部分
let maskLayer = CAShapeLayer()
maskLayer.fillRule = .evenOdd // 奇偶层显示规则
let basicPath = UIBezierPath(rect: view.frame) // 底层
let rect = CGRect(x: borderWidth, y: posTop, width: scanWidth + 4, height: scanWidth + 4)
let maskPath = UIBezierPath(rect: rect) //自定义的遮罩图形
basicPath.append(maskPath) // 重叠
maskLayer.path = basicPath.cgPath
backgroundView.layer.mask = maskLayer
很神奇, 只加了path, 并没有fill颜色, mask也能起到作用
获取当前ViewController
以下代码来自IQKeyboardManagerSwift
/**
Returns the UIViewController object that manages the receiver.
*/
func viewContainingController() -> UIViewController? {
var nextResponder: UIResponder? = self
repeat {
nextResponder = nextResponder?.next
if let viewController = nextResponder as? UIViewController {
return viewController
}
} while nextResponder != nil
return nil
}
/**
Returns the topMost UIViewController object in hierarchy.
*/
func topMostController() -> UIViewController? {
var controllersHierarchy = [UIViewController]()
if var topController = window?.rootViewController {
controllersHierarchy.append(topController)
while let presented = topController.presentedViewController {
topController = presented
controllersHierarchy.append(presented)
}
var matchController: UIResponder? = viewContainingController()
while let mController = matchController as? UIViewController, controllersHierarchy.contains(mController) == false {
repeat {
matchController = matchController?.next
} while matchController != nil && matchController is UIViewController == false
}
return matchController as? UIViewController
} else {
return viewContainingController()
}
}
异步加载图片
import UIKit
// Before
let image = UIImage(named: "big-image")
imageView.image = image
// After
let image = UIImage(named: "big-image")
image?.prepareForDisplay { [weak self] preparedImage in
DispatchQueue.main.async {
self?.imageView.image = preparedImage
}
}
// using Swift Concurrency
let image = UIImage(named: "big-image")
Task {
imageView.image = await image?.byPreparingForDisplay()
}
模拟器发送本地通知
simulator send local notification
import UserNotifications
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in
if granted {
print("授权成功")
} else {
print("授权失败")
}
}
// Create the components that you want to include in the notification.
let content = UNMutableNotificationContent()
content.title = "通知标题"
content.subtitle = "通知副标题"
content.body = "通知内容"
content.sound = UNNotificationSound.default
// Define the date and time for the notification.
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 5, repeats: false)
// 或
// var dateComponents = DateComponents()
// dateComponents.hour = 10 // 10 AM
// let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: false)
// Create the request
let uuidString = UUID().uuidString
let request = UNNotificationRequest(identifier: uuidString, content: content, trigger: trigger)
// Schedule the request with the system.
UNUserNotificationCenter.current().add(request) { error in
if let error = error {
print("添加通知失败:\(error.localizedDescription)")
} else {
print("添加通知成功")
}
}
让普通对象能被枚举
struct Circle {
var radius: Double
static func ~= (pattern: Double, value: Circle) -> Bool {
return value.radius == pattern
}
}
let myCircle = Circle(radius: 5)
switch myCircle {
case 5:
print("Circle with a radius of 5")
case 10:
print("Circle with a radius of 10")
default:
print("circle with a different radius")
}
- 我加了
static才能重载这个操作符 pattern就是case对象- 由于是静态方法,所以当前对象也是需要传进去的, 这个方法定义第二个参数必须是当前类的一个实体
通过这种方法可以少做一些枚举, 直接重载~=操作符即可
break退出嵌套的循环中的指定循环
searchValue: for row in matrix {
for num in row{
if num == valueToFind {
print("Value \(valueToFind)found!")
break searchValue
}
}
}
重载/覆盖父类方法, 并让其不可用
class BaseViewController: UIViewController {
lazy var disposeBag: DisposeBag = .init()
init() {
super.init(nibName: nil, bundle: nil)
}
@available(*, unavailable, message: "We don't support init view controller from a nib.")
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
@available(*, unavailable, message: "We don't support init view controller from a nib.")
required init?(coder: NSCoder) {
fatalError(L10n.Development.fatalErrorInitCoderNotImplemented)
}
}
预览UIKit
到iOS17为止, 有三种写法,
- 一种是实现
UIViewControllerRepresentable - 一种是SwiftUI原生的
PreviewProvider - 一种是新的宏:
#Preview("UIKitPreview")
import SwiftUI
import WidgetKit
struct PreviewSwiftUIKit: UIViewControllerRepresentable {
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
// leave this empty
}
func makeUIViewController(context: Context) -> some UIViewController {
ViewController()
}
}
struct PreviewSwiftUIKit_Previews: PreviewProvider {
static var previews: some View {
Group {
PreviewSwiftUIKit()
}.previewDevice("iPhone 14")
Group {
PreviewSwiftUIKit()
}.previewDevice("iPhone 14 Pro Max")
}
}
#Preview("UIKitPreview") {
return Group {
PreviewSwiftUIKit()
}.previewDevice("iPhone SE")
}
读证书文件
func certificate() -> SecCertificate? {
let filePath = Bundle.main.path(forResource: "yourFileName", ofType: "pem")
if filePath == nil {
return nil
}
let data = try! Data(contentsOf: URL(fileURLWithPath: filePath ?? ""))
let certificate = SecCertificateCreateWithData(nil, data as CFData)!
return certificate
}
/* 上述代码中
SecCertificateCreateWithData(forResource:ofType) 返回 nil
可以转成DER文件:
1. 如果pem文件是个私钥
openssl rsa -pubin -in public.pem -outform der -out cert.der
2. 如果pem文件是一个证书
openssl x509 -in ssl_public_cert.pem -outform der -out ssl_public_cert.cer
*/
UILabel
垂直居中
好像从iOS的哪个版本起, UILabel就支持垂直居中了?
valueLabel.baselineAdjustment = .alignCenters
小组件
小组件里面的内容显示灰方块,
- 可能是因为timeline里的entry添加得过多, 留给小组件计算内容的时间很短, 大概率是超时了(离大谱)
- 也可能是因为没有设置
WidgetFamily的预览(一般不会, 这是多终端的事)
Backlinks