iOS 26 UIKit和Swift上的更新

标识符(Identifier)范围扩展与反引号

详细介绍

  • Swift 6.2 显著扩展了标识符可用字符范围;配合反引号可以使用空格、数字开头或与关键字冲突的名称作为标识符。
  • 典型用途:桥接外部接口命名、演示代码、避免与关键字冲突、为 DSL 提高可读性。
  • 注意:建议仅在必要时使用,保证团队可读性与一致性。

示例代码

swift 复制代码
// 反引号可用于包含空格/数字/关键字等非常规标识符
struct Person {
    var `full name`: String
    var `class`: String // 关键字作为属性名
}

func `run task`(_ value: Int) -> Int { value * 2 }

let 🧭 = "north" // 扩展的可用字符示例(Emoji)
let p = Person(`full name`: "Ada Lovelace", `class`: "VIP")
let output = `run task`(21)
print(p.`full name`, p.`class`, 🧭, output)

字符串插值支持默认值

详细介绍

  • 可选值插入字符串时,可在插值处直接提供默认值:当可选为 nil 时自动使用默认值。
  • 相比以往使用 ?? 的写法,插值默认值语义更直观、噪音更少。

示例代码

swift 复制代码
let nickname: String? = nil
let age: Int? = nil

print("Hi, \(nickname, default: \"Guest\")")               // Guest
print("Age: \(age, default: 18)")                         // 18

let payload: [String: String]? = ["city": "Hangzhou"]
print("City: \(payload?["city"], default: \"Unknown\")")

InlineArray(定长数组)

详细介绍

  • 新增定长数组类型 InlineArray<N, Element>,在栈上紧凑存储,具备更好的性能与缓存局部性,适合小容量、频繁创建/销毁的场景。
  • 可使用类型推断省略容量参数(当字面量可确定大小时)。

示例代码

swift 复制代码
var numbers: InlineArray<4, Int> = [1, 2, 3, 4]
var letters: InlineArray = ["A", "B", "C"]

var sum = 0
for n in numbers { sum += n }
print("sum =", sum) // 10

for (i, ch) in letters.enumerated() {
    print(i, ch)
}

enumerated() 返回类型遵守 Collection

详细介绍

  • enumerated() 的返回类型在 6.2 起遵守 Collection 协议,可直接用于需要集合语义的 API(如 SwiftUI List)。
  • 优势:无需 Array(...) 包装,减少不必要的分配与拷贝。

示例代码

swift 复制代码
let names = ["ZhangSan", "LiSi", "WangWu", "ZhaoLiu"]

// 直接在 enumerated() 上链式使用 Collection 能力(无需 Array(...) 包装)
let evenIndexed = names
    .enumerated()
    .filter { $0.offset % 2 == 0 }
    .map(\.element)

print(evenIndexed) // ["ZhangSan", "WangWu"]

并发编程语义调整与 @concurrent

详细介绍

  • 行为变化:6.2 之前,nonisolated 异步函数会在后台线程执行;6.2 起默认在调用者的 actor 上执行。
  • 新增 @concurrent
    • 让函数在后台线程运行(即使从主线程调用)。
    • 创建与调用者分离的新隔离域。
    • 所有参数与返回值必须符合 Sendable
  • 适用:耗时/CPU 密集或潜在阻塞型任务(大量数据转换、I/O 操作等)。

示例代码

swift 复制代码
actor SomeActor {
  // CPU 密集型任务:后台并发执行,参数/返回须 Sendable
  @concurrent
  nonisolated func heavyCompute(_ input: [Int]) async -> Int {
    input.reduce(0, +)
  }
}

@MainActor
func demo() async {
  let data = Array(0...1_000_00) // 10 万项
  let result = await SomeActor().heavyCompute(data)
  print("result =", result)
}

UIScene 打开外部文件

详细介绍

  • 场景: App 内存在不受自身支持的文件类型,需要委托系统或其他 App 打开。
  • 能力点 : 使用 UIWindowScene.open(_:options:completionHandler:) 打开位于沙盒可访问位置的文件 URL。
  • 要点 :
    • 将 Bundle 文件拷贝至可写目录(如 tmp/)再打开,避免只读路径限制。
    • 首选前台激活的 UIWindowScene;无前台场景时给出用户可理解的降级提示。
    • 处理回调 success 与错误路径,必要时提示"缺少可处理此类型的 App"。

示例代码

swift 复制代码
import UIKit

final class OpenFileViewController: UIViewController {
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard let src = Bundle.main.url(forResource: "sample", withExtension: "zip") else { return }
        openExternally(fileURL: src)
    }

    @MainActor
    private func openExternally(fileURL: URL) {
        let dst = URL.temporaryDirectory.appendingPathComponent(fileURL.lastPathComponent)
        try? FileManager.default.removeItem(at: dst)
        do {
            try FileManager.default.copyItem(at: fileURL, to: dst)
        } catch {
            print("copy failed: \(error)")
            return
        }

        guard let scene = UIApplication.shared.connectedScenes
            .compactMap({ $0 as? UIWindowScene })
            .first(where: { $0.activationState == .foregroundActive }) else {
            print("no active scene")
            return
        }

        scene.open(dst, options: nil) { success in
            print(success ? "已交由系统/他端 App 打开" : "打开失败或无可用 App")
        }
    }
}

UIColor HDR 曝光(Exposure/Linear Exposure)

详细介绍

  • 场景: 在支持 EDR/HDR 的设备上呈现高亮度色彩与过曝细节。
  • 能力点 : UIColor 新增 exposurelinearExposure 构造,UIColorWellUIColorPickerViewController 支持 HDR 选择。
  • 要点 :
    • HDR 显示依赖硬件与系统显示设置,SDR 屏幕回退为常规显示。
    • 可以设置 maximumLinearExposure 限定取色上限;吸管可通过 supportsEyedropper 控制。

示例代码

swift 复制代码
import UIKit

final class HDRColorViewController: UIViewController {
    private lazy var colorWell: UIColorWell = {
        let well = UIColorWell()
        well.title = "HDR 背景"
        well.maximumLinearExposure = 2.0
        well.supportsEyedropper = false
        well.addTarget(self, action: #selector(onColor), for: .valueChanged)
        return well
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = UIColor(red: 1, green: 0, blue: 0, alpha: 1, exposure: 2.5)
        view.addSubview(colorWell)
        colorWell.center = view.center
        colorWell.sizeToFit()
    }

    @objc private func onColor() {
        view.backgroundColor = colorWell.selectedColor
    }
}

UISlider 刻度与样式(TrackConfiguration/Style)

详细介绍

  • 场景: 需要"离散刻度"与"无拇指轨迹"选择器体验(音量档位、配置项选择)。
  • 能力点 : sliderStyletrackConfiguration 支持内置刻度与自定义刻度,并可仅允许落在刻度上。
  • 要点 :
    • numberOfTicks 快速生成均分刻度;或提供 ticks 自定义不均匀刻度。
    • allowsTickValuesOnly=true 时配合手动"吸附"提升易用性。
    • neutralValue 可以设定一个基准点,让进度条可以分成左右两个进度,类似音效均衡器中的默认值,或参数的零点
    • enabledRange 定义滑块的有效范围,范围之外不可以交互

示例代码

swift 复制代码
import UIKit

final class TickedSliderViewController: UIViewController {
    private lazy var slider: UISlider = {
        let s = UISlider()
        s.sliderStyle = .default
        var cfg = UISlider.trackConfiguration = UISlider.TrackConfiguration(
        allowsTickValuesOnly: true,
        neutralValue: 0.5,
        enabledRange: 0...1,
        numberOfTicks: 11 // 0.0 ~ 1.0, 11 个刻度
    ) 
        cfg.allowsTickValuesOnly = true
        s.trackConfiguration = cfg
        s.addTarget(self, action: #selector(onChange), for: .valueChanged)
        return s
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        slider.frame = CGRect(x: 20, y: view.center.y, width: view.bounds.width - 40, height: 44)
        view.addSubview(slider)
    }

    @objc private func onChange(_ sender: UISlider) {
        print(slider.value)
        // 吸附到最近刻度,测试enabledRange时注释下面代码
        let ticks: Float = 10
        sender.value = round(sender.value * ticks) / ticks
    }
}

UIViewController 转场 zoom 支持 UIBarButtonItem 触发

详细介绍

  • 场景: 以导航栏按钮为触发源的 zoom 转场,突出从按钮"放大"至目标页的空间感。
  • 要点 :
    • 设置 preferredTransition = .zoom { ... } 并返回触发的 UIBarButtonItem
    • 保证回调中的 zoomedViewController 类型正确。

示例代码

swift 复制代码
import UIKit

final class ZoomSourceVC: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        navigationItem.rightBarButtonItem = UIBarButtonItem(systemItem: .add, primaryAction: UIAction { [weak self] _ in
            let next = UIViewController()
            next.view.backgroundColor = .systemPink
            next.preferredTransition = .zoom { context in
                guard context.zoomedViewController === next else { return nil }
                return self?.navigationItem.rightBarButtonItem
            }
            self?.present(next, animated: true)
        })
    }
}

UIButton 的 Liquid Glass 与 SF Symbols 动画切换

详细介绍

  • 要点:
  • UIButton的Configuration新增glass、clearGlass、prominentGlass、prominentClearGlass方法,实现 Liquid Glass 风格。
  • 新增symbolContentTransition 实现 SF Symbols 的带动画替换。
  • 能力点 :
    • UIButton.Configuration.glass()
    • UIButton.Configuration.clearGlass()
    • UIButton.Configuration.prominentGlass()
    • UIButton.Configuration.prominentClearGlass()
    • symbolContentTransition

示例代码

swift 复制代码
import UIKit

final class GlassButtonViewController: UIViewController {
    private let button = UIButton(type: .system)

    override func viewDidLoad() {
        super.viewDidLoad()
        var cfg = UIButton.Configuration.prominentGlass()
        cfg.title = "点赞"
        cfg.image = UIImage(systemName: "hand.thumbsup")
        cfg.preferredSymbolConfigurationForImage = .init(pointSize: 20, weight: .regular)
        cfg.symbolContentTransition = UISymbolContentTransition(.replace, options: .speed(0.12))
        button.configuration = cfg
        button.isSymbolAnimationEnabled = true
        button.addAction(UIAction { [weak self] _ in self?.toggle() }, for: .primaryActionTriggered)
        button.frame = CGRect(x: 100, y: 200, width: 180, height: 56)
        view.addSubview(button)
    }

    private func toggle() {
        let filled = (button.configuration?.image == UIImage(systemName: "hand.thumbsup.fill"))
        button.configuration?.image = UIImage(systemName: filled ? "hand.thumbsup" : "hand.thumbsup.fill")
    }
}

UIVisualEffectView 的 UIGlassEffect 与 Container

详细介绍

  • 场景 : - UIGlassEffect 是 iOS 26 引入的 UIVisualEffect 的子类,用来呈现系统级的 "Liquid Glass(液态玻璃)"材质:半透明、折射、高光、随环境和尺寸自适应的玻璃视觉效果。它通常与 UIVisualEffectView 配合使用。
  • 能力点 :
    • 外观样式:支持至少 .regular 与 .clear 等玻璃样式,视觉随暗/亮模式与背景内容自动适配(更大尺寸更不透明、更小尺寸更清晰)
    • 可交互性(isInteractive) :设置为 true 后,系统会为玻璃上的交互元素提供内建触感反馈、缩放 / 弹跳等交互行为,使自定义控件与系统控件在交互感受上一致
    • 着色(tintColor) :可为玻璃设置 tintColor,系统会自动生成"vibrant"版本供玻璃上的内容使用,便于做高亮或品牌色
    • 角与形状(cornerConfiguration) :Glass 默认是胶囊(capsule)形状;WWDC 中演示了 cornerConfiguration 用于自定义圆角或相对于容器自动适配的行为(例如 .containerRelative),使玻璃在接近容器角时自动改变角半径。注意:beta 迭代中该 API 的表现可能有调整
    • 容器/合并(UIGlassContainerEffect) :支持把多个 glass 元素放进一个容器进行合成与"合并"动画(小玻璃靠近时会像水滴合并),用 UIGlassContainerEffect + UIVisualEffectView 进行组织

示例代码

swift 复制代码
final class GlassEffectViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        makeGlassContainers()
        makeLiquidGlassExample()
    }
    func makeGlassContainers() {
        // 创建容器 effect(多个 glass 元素会合并视觉)
           let container = UIGlassContainerEffect()
           let containerView = UIVisualEffectView(effect: container)
           containerView.frame = view.bounds
           containerView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
           view.addSubview(containerView)

           // 创建两个玻璃子项
           func makeGlassElement(frame: CGRect, tint: UIColor) -> UIVisualEffectView {
               let e = UIGlassEffect(style: .regular)
               e.tintColor = tint
               e.isInteractive = **false******
               let v = UIVisualEffectView(effect: e)
               v.frame = frame
               v.layer.cornerRadius = frame.height / 2
               v.clipsToBounds = **true******
               return v
           }
           let a = makeGlassElement(frame: CGRect(x: 60, y: 200, width: 120, height: 50), tint: .systemBlue)
           let b = makeGlassElement(frame: CGRect(x: 180, y: 200, width: 120, height: 50), tint: .systemPink)
           containerView.contentView.addSubview(a)
           containerView.contentView.addSubview(b)
    }
    // 在 UIViewController 中示例
    **func** makeLiquidGlassExample() {
        guard #available(iOS 26.0, *) else {
            // 回退:普通模糊
            let blur = UIBlurEffect(style: .systemMaterial)
            let blurView = UIVisualEffectView(effect: blur)
            blurView.frame = CGRect(x: 40, y: 120, width: 240, height: 72)
            blurView.layer.cornerRadius = 12
            blurView.clipsToBounds = true
            view.addSubview(blurView)
            return
        }

        // 创建玻璃 effect(可设置 style)
        **let** glassEffect = UIGlassEffect(style: .regular)
        glassEffect.tintColor = .systemGray5
        glassEffect.isInteractive = true       // 启用交互感
        
        // 使用 UIVisualEffectView 承载 effect
        let glassView = UIVisualEffectView(effect: glassEffect) // 先为 nil,稍后动画 materialize
        glassView.frame = CGRect(x: 40, y: 120, width: 240, height: 72)
        glassView.layer.cornerRadius = 12
        glassView.clipsToBounds = true

        // 在 contentView 添加内容(label 会自动成为 vibrant)
        let label = UILabel(frame: glassView.contentView.bounds.insetBy(dx: 12, dy: 8))
        label.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        label.text = "Liquid Glass Button"
        label.textAlignment = .center
        label.textColor = .label
        glassView.contentView.addSubview(label)
        view.addSubview(glassView)
    }
}

UIImageView Symbol Animations:drawOn/drawOff

详细介绍

  • 场景: 更具"手绘描边感"的开关式动画效果展示。
  • 要点 : 使用 addSymbolEffect(.drawOn/.drawOff, options: .speed(_)),搭配合适的 SymbolConfiguration

示例代码

swift 复制代码
import UIKit

final class SymbolDrawViewController: UIViewController {
    private let imageView: UIImageView = {
        let cfg = UIImage.SymbolConfiguration(pointSize: 96, weight: .thin)
        return UIImageView(image: UIImage(systemName: "bolt", withConfiguration: cfg))
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        imageView.center = view.center
        imageView.frame.size = CGSize(width: 160, height: 160)
        view.addSubview(imageView)
    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        imageView.addSymbolEffect(.drawOff, options: .speed(0.12))
        DispatchQueue.main.asyncAfter(deadline: .now() + 1.2) {
            self.imageView.addSymbolEffect(.drawOn, options: .speed(0.12))
        }
    }
}

UIView 圆角配置 UICornerConfiguration(可动画)

详细介绍

  • 能力点 : 通过 cornerConfiguration 以组合/胶囊/均匀/单边等方式定义圆角,且可被动画过渡。
  • 用法 : capsule()uniformCorners(radius:)corners(topLeftRadius:...)uniformEdges(leftRadius:rightRadius:)

示例代码

swift 复制代码
import UIKit

final class CornerConfigViewController: UIViewController {
    private let demo = UIView(frame: CGRect(x: 140, y: 200, width: 120, height: 120))

    override func viewDidLoad() {
        super.viewDidLoad()
        demo.backgroundColor = .systemBlue
        demo.cornerConfiguration = .uniformCorners(radius: 12)
        view.addSubview(demo)
    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        UIView.animate(withDuration: 1) {
            self.demo.cornerConfiguration = .corners(topLeftRadius: 6, topRightRadius: 24, bottomLeftRadius: 24, bottomRightRadius: 6)
        }completion: { _ in
            UIView.animate(withDuration: 1) {
                self.demo.cornerConfiguration = .capsule()
            }completion: { _ in
                UIView.animate(withDuration: 1) {
                    self.demo.cornerConfiguration = .uniformEdges(leftRadius: 25, rightRadius: 29)
                }completion: { _ in
                    UIView.animate(withDuration: 1) {
                        self.demo.cornerConfiguration = .uniformEdges(topRadius: 12, bottomRadius: 20)
                    }
                }
            }
        }
    }
}

动画选项 .flushUpdates(自动追踪变更)

详细介绍

  • 能力点 : .flushUpdates 自动追踪 @Observable 数据或 AutoLayout 约束变更并添加动画,无需手动 layoutIfNeeded()
  • 建议 : 数据驱动优先;必要时配合 UIViewPropertyAnimator

示例代码

swift 复制代码
import UIKit

@Observable final class Model { var bg: UIColor = .systemGray }

final class FlushUpdatesViewController: UIViewController {
    private let box = UIView()
    private var w: NSLayoutConstraint!
    private var h: NSLayoutConstraint!
    private let model = Model()

    override func viewDidLoad() {
        super.viewDidLoad()
        box.backgroundColor = .systemRed
        box.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(box)
        w = box.widthAnchor.constraint(equalToConstant: 80)
        h = box.heightAnchor.constraint(equalToConstant: 80)
        NSLayoutConstraint.activate([
            w, h,
            box.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            box.centerYAnchor.constraint(equalTo: view.centerYAnchor)
        ])
    }

    override func viewWillLayoutSubviews() {
        super.viewWillLayoutSubviews()
        view.backgroundColor = model.bg
    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        UIView.animate(withDuration: 1.0, delay: 0, options: .flushUpdates) {
            self.model.bg = .systemBlue
        } completion: { _ in
            _ = UIViewPropertyAnimator.runningPropertyAnimator(withDuration: 1.0, delay: 0, options: .flushUpdates) {
                self.w.constant = 220
                self.h.constant = 220
            }
        }
    }
}

UITabBarController 最小化行为与底部辅助视图

详细介绍

  • 能力点 :
    • tabBarMinimizeBehavior = .onScrollDown:向下滚动时仅保留首个 Tab 与搜索 Tab 图标,中部显示 UITabAccessory
    • bottomAccessory:为 TabBar 上方增设工具条等辅助视图。

示例代码

swift 复制代码
import UIKit

final class TabsVC: UITabBarController {
    override func viewDidLoad() {
        super.viewDidLoad()
        tabs.append(configTab(title: "聊天", image: "message", id: "chats"))
        tabs.append(configTab(title: "通讯录", image: "person.2", id: "contacts"))
        tabs.append(configTab(title: "发现", image: "safari", id: "discover"))
        tabs.append(configTab(title: "我", image: "person", id: "me"))
        tabs.append(configSearchTab(title: "搜索"))
        selectedTab = tabs.last
        tabBarMinimizeBehavior = .onScrollDown
        bottomAccessory = UITabAccessory(contentView: UIToolbar())
    }

    private func configTab(title: String, image: String, id: String) -> UITab {
        UITab(title: title, image: UIImage(systemName: image), identifier: id) { _ in
            let vc = UIViewController()
            let scroll = UIScrollView(frame: UIScreen.main.bounds)
            scroll.backgroundColor = .secondarySystemBackground
            scroll.contentSize = CGSize(width: UIScreen.main.bounds.width, height: 1600)
            vc.view.addSubview(scroll)
            return UINavigationController(rootViewController: vc)
        }
    }

    private func configSearchTab(title: String) -> UISearchTab {
        UISearchTab { _ in
            let vc = UIViewController()
            vc.view.backgroundColor = .systemBackground
            return UINavigationController(rootViewController: vc)
        }
    }
}

详细介绍

  • 场景 : iPadOS 具备 macOS 风格菜单栏,支持注入快捷键与自定义 UIMenu
  • 要点 : 通过 UIMainMenuSystem.shared.setBuildConfiguration 在运行时构建或替换菜单层级。

示例代码

swift 复制代码
import UIKit

final class MenuBarViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        let config = UIMainMenuSystem.Configuration()
        UIMainMenuSystem.shared.setBuildConfiguration(config) { builder in
            let refresh = UIKeyCommand(input: "R", modifierFlags: [.command], action: #selector(self.refresh))
            refresh.title = "Refresh"
            refresh.image = UIImage(systemName: "arrow.clockwise")
            builder.insertElements([refresh], beforeMenu: .about)

            let sort = UIMenu(title: "Sort", children: [
                UICommand(title: "By Name", action: #selector(self.sortByName)),
                UICommand(title: "By Date", action: #selector(self.sortByDate))
            ])
            builder.insertSibling(sort, afterMenu: .help)
        }
    }

    @objc private func refresh() { view.backgroundColor = .systemTeal }
    @objc private func sortByName() { view.backgroundColor = .systemGreen }
    @objc private func sortByDate() { view.backgroundColor = .systemOrange }
}

Update Properties 轻量 UI 更新

详细介绍

  • 能力点 : UIViewController.updateProperties() / UIView.updateProperties() 用于不触发布局的轻量 UI 更新。
  • 适合 : 修改文本/颜色/可见性这类不需要触发 layoutSubviews() 的更新。
  • 补充 : setNeedsUpdateProperties() 可手动请求一次更新;与 @Observable 协同可自动追踪。

示例代码

swift 复制代码
import UIKit

@Observable final class BannerModel { var text = "Hello"; var color: UIColor = .label }

final class UpdatePropsViewController: UIViewController {
    private let label = UILabel()
    private let model = BannerModel()

    override func viewDidLoad() {
        super.viewDidLoad()
        label.font = .systemFont(ofSize: 40, weight: .bold)
        label.textAlignment = .center
        label.frame = CGRect(x: 0, y: 0, width: view.bounds.width, height: 100)
        label.center = view.center
        view.addSubview(label)
    }

    override func updateProperties() {
        super.updateProperties()
        label.text = model.text
        label.textColor = model.color
    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        model.text = "iOS26"
        model.color = .systemBlue
        // 手动触发一次
        setNeedsUpdateProperties()
    }
}

UIKit 支持 @Observable(自动追踪 UI 更新)

详细介绍

  • 概念 : @Observable 是一个 属性包装器(Property Wrapper) ,用于标记类或结构体,使其内部属性 自动可观察(observable) 。被标记的对象会自动生成 可监听的事件,UI 或其他订阅者可以自动响应属性变化,而不必手动发布通知。类似 Combine 的 @Published + ObservableObject,但不依赖 Combine,更加轻量和原生。
  • 能力点 : UIKit 直接追踪 @Observable 类实例属性变化,自动驱动 layoutSubviews()/viewWillLayoutSubviews().flushUpdates 动画。
  • 兼容性 : 向下可至 iOS 18,需在 Info.plist 添加 UIObservationTrackingEnabled=YES

示例代码

swift 复制代码
import UIKit

@Observable final class PhoneModel { var name: String; var os: String; init(name: String, os: String) { self.name = name; self.os = os } }

final class PhoneCell: UITableViewCell {
    var model: PhoneModel? { didSet { setNeedsLayout() } }
    private let nameLabel = UILabel(); private let osLabel = UILabel()
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        [nameLabel, osLabel].forEach { contentView.addSubview($0) }
        nameLabel.font = .boldSystemFont(ofSize: 22); osLabel.font = .systemFont(ofSize: 16)
    }
    required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
    override func layoutSubviews() {
        super.layoutSubviews()
        nameLabel.frame = CGRect(x: 20, y: 12, width: bounds.width - 40, height: 28)
        osLabel.frame = CGRect(x: 20, y: 44, width: bounds.width - 40, height: 22)
        nameLabel.text = model?.name; osLabel.text = model?.os
    }
}

final class PhoneListVC: UIViewController, UITableViewDataSource {
    private let table = UITableView(frame: .zero, style: .plain)
    private let data = [
        PhoneModel(name: "iPhone 16", os: "iOS 18"),
        PhoneModel(name: "iPhone 16 Pro", os: "iOS 18")
    ]
    override func viewDidLoad() {
        super.viewDidLoad()
        table.dataSource = self; table.rowHeight = 78
        table.register(PhoneCell.self, forCellReuseIdentifier: "cell")
        table.frame = view.bounds; view.addSubview(table)
        DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
            self.data[0].name = "iPhone 17"; self.data[0].os = "iOS 26"
        }
    }
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { data.count }
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! PhoneCell
        cell.model = data[indexPath.row]
        return cell
    }
}

强类型通知(NotificationCenter.MainActorMessage/AsyncMessage)

详细介绍

  • 能力点 : 编译期类型检查替代字符串通知名 + userInfo 方式,提升线程/类型安全。
  • 两类 : 主线程消息 MainActorMessage 与异步消息 AsyncMessage

示例代码

swift 复制代码
import UIKit

public final class NotifySubject { static let shared = NotifySubject() }

public struct TitleChanged: NotificationCenter.MainActorMessage {
    public typealias Subject = NotifySubject
    public static var name: Notification.Name { .init("TitleChanged") }
    let title: String
}

final class TypedNotificationVC: UIViewController {
    private var token: NotificationCenter.ObservationToken!
    private let label = UILabel()

    override func viewDidLoad() {
        super.viewDidLoad()
        label.frame = CGRect(x: 0, y: 0, width: view.bounds.width, height: 60)
        label.center = view.center; label.textAlignment = .center; view.addSubview(label)
        token = NotificationCenter.default.addObserver(of: NotifySubject.shared, for: TitleChanged.self) { [weak self] msg in
            self?.label.text = msg.title
        }
    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
       Task {
            //子线程发送,在主线程收到
            NotificationCenter.default.post(TitleChanged(title: "Updated"), subject: NotifySubject.shared)

        }
    }

    deinit { NotificationCenter.default.removeObserver(token) }
}
相关推荐
YungFan4 小时前
iOS26适配指南之UISearchController
ios·swift
陈彬技术实践5 小时前
从 Auto Layout 原理看:为什么 UITableView.tableHeaderView 无法自动撑开?
ios
2501_9159090612 小时前
tcpdump 抓包数据分析实战,命令、过滤、常见故障定位与真机补充流程
网络·测试工具·ios·小程序·uni-app·iphone·tcpdump
Digitally15 小时前
如何将iPhone上的HEIF图像下载到电脑
ios·iphone
书弋江山15 小时前
iOS一直讲的单元格优化
macos·ios·cocoa
00后程序员张19 小时前
tcpdump 抓包分析,命令、过滤技巧、常见症状定位与移动真机补充方案
网络·测试工具·ios·小程序·uni-app·iphone·tcpdump
2501_9293826519 小时前
iphone IOS3~IOS9游戏 旧iphone 单机游戏合集分享
游戏·ios·iphone
2501_915921431 天前
iOS 26 电耗监测与优化,耗电问题实战 + 多工具 辅助策略
android·macos·ios·小程序·uni-app·cocoa·iphone
2501_915921431 天前
苹果软件混淆与 iOS 应用加固白皮书,IPA 文件加密、反编译防护与无源码混淆方案全解析
android·ios·小程序·https·uni-app·iphone·webview