MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

SwiftUI 与NotificationCenter通知

2023-05-246.7k 阅读

SwiftUI 与 NotificationCenter 通知基础概念

在 iOS 开发中,NotificationCenter 是一种非常强大的机制,它允许应用程序中的不同对象之间进行通信。这种通信方式是基于观察者模式的,一个对象(发布者)可以发布通知,而其他对象(观察者)可以注册接收特定类型的通知。这样,当发布者发布通知时,所有注册接收该通知的观察者都会收到通知并执行相应的操作。

SwiftUI 是苹果推出的用于构建用户界面的现代框架。它提供了一种声明式的方式来创建 UI,使得代码更加简洁和易于理解。虽然 SwiftUI 主要关注 UI 的构建,但在实际应用中,与 NotificationCenter 的交互也是非常常见的需求。例如,当设备的方向发生改变、键盘出现或消失等系统事件发生时,我们可能需要在 SwiftUI 视图中做出相应的响应。

在 SwiftUI 中使用 NotificationCenter

  1. 导入必要的库 在 SwiftUI 项目中使用 NotificationCenter,首先需要导入 UIKit 库,因为 NotificationCenterUIKit 框架的一部分。在 SwiftUI 的视图文件中,添加如下导入语句:
import UIKit
import SwiftUI
  1. 注册通知观察者 在 SwiftUI 视图中注册通知观察者,可以在 onAppear 修饰符中进行。例如,我们要监听设备方向改变的通知,代码如下:
struct ContentView: View {
    var body: some View {
        VStack {
            Text("Device Orientation Example")
        }
       .onAppear {
            NotificationCenter.default.addObserver(self, selector: #selector(handleDeviceOrientationChange), name: UIDevice.orientationDidChangeNotification, object: nil)
        }
       .onDisappear {
            NotificationCenter.default.removeObserver(self, name: UIDevice.orientationDidChangeNotification, object: nil)
        }
    }

    @objc func handleDeviceOrientationChange() {
        let orientation = UIDevice.current.orientation
        if orientation.isLandscape {
            print("Device is in landscape orientation")
        } else if orientation.isPortrait {
            print("Device is in portrait orientation")
        }
    }
}

在上述代码中,onAppear 修饰符在视图出现时注册了一个通知观察者,监听 UIDevice.orientationDidChangeNotification 通知。当接收到该通知时,会调用 handleDeviceOrientationChange 方法。onDisappear 修饰符则在视图消失时移除观察者,以避免内存泄漏。

自定义通知

除了监听系统通知,我们还可以自定义通知,以便在应用程序内部不同模块之间进行通信。

  1. 定义自定义通知名称 首先,在项目的某个全局文件中定义自定义通知的名称。例如,在 AppConstants.swift 文件中:
struct AppConstants {
    static let customNotificationName = Notification.Name("CustomNotification")
}
  1. 发布自定义通知 在需要发布通知的地方,使用 NotificationCenter.default.post 方法发布通知。例如,在一个视图模型中:
class CustomViewModel: ObservableObject {
    func sendCustomNotification() {
        NotificationCenter.default.post(name: AppConstants.customNotificationName, object: nil)
    }
}
  1. 接收自定义通知 在 SwiftUI 视图中注册接收自定义通知,代码如下:
struct CustomNotificationView: View {
    @ObservedObject var viewModel = CustomViewModel()

    var body: some View {
        VStack {
            Button("Send Custom Notification") {
                self.viewModel.sendCustomNotification()
            }
        }
       .onAppear {
            NotificationCenter.default.addObserver(self, selector: #selector(handleCustomNotification), name: AppConstants.customNotificationName, object: nil)
        }
       .onDisappear {
            NotificationCenter.default.removeObserver(self, name: AppConstants.customNotificationName, object: nil)
        }
    }

    @objc func handleCustomNotification() {
        print("Received custom notification")
    }
}

在这个例子中,当点击按钮时,视图模型会发布自定义通知。视图在出现时注册接收该通知,接收到通知后会在控制台打印消息。

传递通知数据

在实际应用中,我们通常需要在通知中传递一些数据。

  1. 发布带数据的通知 修改发布通知的代码,传递数据。例如,我们传递一个字符串:
class DataPassingViewModel: ObservableObject {
    func sendDataNotification() {
        let userInfo = ["message": "This is a data - carrying notification"] as [String : Any]
        NotificationCenter.default.post(name: AppConstants.customNotificationName, object: nil, userInfo: userInfo)
    }
}
  1. 接收带数据的通知 在接收通知的方法中获取传递的数据:
struct DataPassingView: View {
    @ObservedObject var viewModel = DataPassingViewModel()

    var body: some View {
        VStack {
            Button("Send Data - carrying Notification") {
                self.viewModel.sendDataNotification()
            }
        }
       .onAppear {
            NotificationCenter.default.addObserver(self, selector: #selector(handleDataNotification), name: AppConstants.customNotificationName, object: nil)
        }
       .onDisappear {
            NotificationCenter.default.removeObserver(self, name: AppConstants.customNotificationName, object: nil)
        }
    }

    @objc func handleDataNotification(notification: Notification) {
        if let userInfo = notification.userInfo, let message = userInfo["message"] as? String {
            print("Received data: \(message)")
        }
    }
}

这样,我们就可以在通知中传递并接收数据,实现更灵活的通信。

使用 Combine 框架简化通知处理

Combine 是苹果推出的响应式编程框架,它可以简化 NotificationCenter 的使用。

  1. 导入 Combine 框架 在视图文件中导入 Combine:
import Combine
  1. 使用 Combine 订阅通知 以监听键盘出现通知为例:
struct KeyboardView: View {
    @State private var isKeyboardVisible = false
    private var cancellables = Set<AnyCancellable>()

    var body: some View {
        VStack {
            if isKeyboardVisible {
                Text("Keyboard is visible")
            } else {
                Text("Keyboard is hidden")
            }
        }
       .onAppear {
            NotificationCenter.default.publisher(for: UIResponder.keyboardWillShowNotification)
               .subscribe(on: DispatchQueue.main)
               .sink { _ in
                    self.isKeyboardVisible = true
                }
               .store(in: &self.cancellables)

            NotificationCenter.default.publisher(for: UIResponder.keyboardWillHideNotification)
               .subscribe(on: DispatchQueue.main)
               .sink { _ in
                    self.isKeyboardVisible = false
                }
               .store(in: &self.cancellables)
        }
    }
}

在上述代码中,通过 NotificationCenter.default.publisher(for:) 方法创建通知发布者,然后使用 sink 方法订阅通知并执行相应的操作。cancellables 用于存储订阅,以确保在视图消失时取消订阅,避免内存泄漏。

在 SwiftUI 视图模型中使用 NotificationCenter

  1. 在视图模型中注册通知 有时候,将通知处理逻辑放在视图模型中会使代码结构更清晰。例如:
class NotificationViewModel: ObservableObject {
    @Published var deviceOrientation: UIDeviceOrientation =.unknown

    init() {
        NotificationCenter.default.addObserver(self, selector: #selector(handleDeviceOrientationChange), name: UIDevice.orientationDidChangeNotification, object: nil)
    }

    deinit {
        NotificationCenter.default.removeObserver(self, name: UIDevice.orientationDidChangeNotification, object: nil)
    }

    @objc func handleDeviceOrientationChange() {
        deviceOrientation = UIDevice.current.orientation
    }
}
  1. 在 SwiftUI 视图中使用视图模型
struct ViewModelNotificationView: View {
    @ObservedObject var viewModel = NotificationViewModel()

    var body: some View {
        VStack {
            if viewModel.deviceOrientation.isLandscape {
                Text("Landscape Orientation")
            } else if viewModel.deviceOrientation.isPortrait {
                Text("Portrait Orientation")
            }
        }
    }
}

在这个例子中,视图模型负责注册和处理通知,并通过 @Published 属性发布通知相关的数据变化,视图则观察视图模型的变化并做出相应的 UI 更新。

注意事项

  1. 内存管理 在使用 NotificationCenter 时,一定要注意内存管理。确保在视图消失或对象不再需要接收通知时,及时移除观察者,否则可能会导致内存泄漏。
  2. 线程安全 NotificationCenter 发布的通知可能在不同的线程上触发。如果在通知处理方法中需要更新 UI,一定要确保在主线程上进行操作。例如,在 Combine 订阅通知时,使用 subscribe(on: DispatchQueue.main) 确保操作在主线程执行。
  3. 通知命名规范 对于自定义通知,要遵循良好的命名规范,避免与系统通知或其他模块的自定义通知发生冲突。通常可以使用应用程序的命名空间来命名自定义通知。

结合 SwiftUI 的响应式设计

SwiftUI 的设计理念与响应式编程高度契合,而 NotificationCenter 可以作为响应式设计的一部分。通过监听通知并做出相应的 UI 或数据更新,我们可以实现更加动态和交互性强的应用程序。例如,当应用程序接收到低电量通知时,可以动态调整 UI 以节省电量,如降低屏幕亮度、停止一些后台任务等。

不同场景下的应用案例

  1. 多视图间通信 假设我们有一个主视图和一个子视图,主视图中的某些操作需要通知子视图进行更新。我们可以通过自定义通知实现这种跨视图的通信。主视图发布通知,子视图注册接收通知并根据通知内容更新自身的 UI 或数据。
  2. 与系统服务集成 在开发一些需要与系统服务紧密结合的应用时,如日历应用、位置服务应用等,NotificationCenter 可以帮助我们监听系统服务相关的通知。例如,日历应用可以监听日历事件更改的通知,及时更新应用内的日程显示。

性能优化

  1. 减少不必要的通知监听 只在真正需要的地方注册通知观察者,避免在应用的各个角落都注册相同的通知,这样可以减少系统开销。
  2. 优化通知处理逻辑 通知处理方法中的代码应该尽量简洁高效,避免在其中执行复杂的计算或长时间运行的任务,以免影响应用的响应性能。

总结 SwiftUI 与 NotificationCenter 的结合使用

SwiftUI 与 NotificationCenter 的结合为开发者提供了一种强大的机制,用于在应用程序中实现不同组件之间的通信以及与系统事件的交互。通过合理使用 NotificationCenter,我们可以使 SwiftUI 应用更加灵活、动态和响应迅速。无论是监听系统通知还是自定义通知,都需要注意内存管理、线程安全等问题,以确保应用程序的稳定性和性能。同时,结合 Combine 框架可以进一步简化通知处理逻辑,使代码更加简洁和易于维护。在实际开发中,根据不同的应用场景,充分利用这一机制,可以打造出用户体验良好的 iOS 应用。

示例代码汇总

  1. 设备方向改变通知示例
import UIKit
import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack {
            Text("Device Orientation Example")
        }
       .onAppear {
            NotificationCenter.default.addObserver(self, selector: #selector(handleDeviceOrientationChange), name: UIDevice.orientationDidChangeNotification, object: nil)
        }
       .onDisappear {
            NotificationCenter.default.removeObserver(self, name: UIDevice.orientationDidChangeNotification, object: nil)
        }
    }

    @objc func handleDeviceOrientationChange() {
        let orientation = UIDevice.current.orientation
        if orientation.isLandscape {
            print("Device is in landscape orientation")
        } else if orientation.isPortrait {
            print("Device is in portrait orientation")
        }
    }
}
  1. 自定义通知示例
// AppConstants.swift
struct AppConstants {
    static let customNotificationName = Notification.Name("CustomNotification")
}

// CustomViewModel.swift
class CustomViewModel: ObservableObject {
    func sendCustomNotification() {
        NotificationCenter.default.post(name: AppConstants.customNotificationName, object: nil)
    }
}

// CustomNotificationView.swift
struct CustomNotificationView: View {
    @ObservedObject var viewModel = CustomViewModel()

    var body: some View {
        VStack {
            Button("Send Custom Notification") {
                self.viewModel.sendCustomNotification()
            }
        }
       .onAppear {
            NotificationCenter.default.addObserver(self, selector: #selector(handleCustomNotification), name: AppConstants.customNotificationName, object: nil)
        }
       .onDisappear {
            NotificationCenter.default.removeObserver(self, name: AppConstants.customNotificationName, object: nil)
        }
    }

    @objc func handleCustomNotification() {
        print("Received custom notification")
    }
}
  1. 传递通知数据示例
// DataPassingViewModel.swift
class DataPassingViewModel: ObservableObject {
    func sendDataNotification() {
        let userInfo = ["message": "This is a data - carrying notification"] as [String : Any]
        NotificationCenter.default.post(name: AppConstants.customNotificationName, object: nil, userInfo: userInfo)
    }
}

// DataPassingView.swift
struct DataPassingView: View {
    @ObservedObject var viewModel = DataPassingViewModel()

    var body: some View {
        VStack {
            Button("Send Data - carrying Notification") {
                self.viewModel.sendDataNotification()
            }
        }
       .onAppear {
            NotificationCenter.default.addObserver(self, selector: #selector(handleDataNotification), name: AppConstants.customNotificationName, object: nil)
        }
       .onDisappear {
            NotificationCenter.default.removeObserver(self, name: AppConstants.customNotificationName, object: nil)
        }
    }

    @objc func handleDataNotification(notification: Notification) {
        if let userInfo = notification.userInfo, let message = userInfo["message"] as? String {
            print("Received data: \(message)")
        }
    }
}
  1. Combine 框架处理通知示例
import Combine
import UIKit
import SwiftUI

struct KeyboardView: View {
    @State private var isKeyboardVisible = false
    private var cancellables = Set<AnyCancellable>()

    var body: some View {
        VStack {
            if isKeyboardVisible {
                Text("Keyboard is visible")
            } else {
                Text("Keyboard is hidden")
            }
        }
       .onAppear {
            NotificationCenter.default.publisher(for: UIResponder.keyboardWillShowNotification)
               .subscribe(on: DispatchQueue.main)
               .sink { _ in
                    self.isKeyboardVisible = true
                }
               .store(in: &self.cancellables)

            NotificationCenter.default.publisher(for: UIResponder.keyboardWillHideNotification)
               .subscribe(on: DispatchQueue.main)
               .sink { _ in
                    self.isKeyboardVisible = false
                }
               .store(in: &self.cancellables)
        }
    }
}
  1. 视图模型中使用 NotificationCenter 示例
// NotificationViewModel.swift
class NotificationViewModel: ObservableObject {
    @Published var deviceOrientation: UIDeviceOrientation =.unknown

    init() {
        NotificationCenter.default.addObserver(self, selector: #selector(handleDeviceOrientationChange), name: UIDevice.orientationDidChangeNotification, object: nil)
    }

    deinit {
        NotificationCenter.default.removeObserver(self, name: UIDevice.orientationDidChangeNotification, object: nil)
    }

    @objc func handleDeviceOrientationChange() {
        deviceOrientation = UIDevice.current.orientation
    }
}

// ViewModelNotificationView.swift
struct ViewModelNotificationView: View {
    @ObservedObject var viewModel = NotificationViewModel()

    var body: some View {
        VStack {
            if viewModel.deviceOrientation.isLandscape {
                Text("Landscape Orientation")
            } else if viewModel.deviceOrientation.isPortrait {
                Text("Portrait Orientation")
            }
        }
    }
}

通过上述详细的介绍和丰富的代码示例,相信开发者能够深入理解并熟练运用 SwiftUI 与 NotificationCenter 进行高效的 iOS 应用开发。在实际项目中,根据具体需求合理选择和优化通知机制的使用,将有助于提升应用的质量和用户体验。无论是简单的系统通知监听,还是复杂的自定义通知和数据传递,都可以通过本文所介绍的方法和技巧来实现。同时,不断关注苹果官方文档和最新技术动态,有助于在开发中更好地利用这些强大的功能。在处理通知时,始终要牢记内存管理和线程安全的重要性,确保应用程序的稳定性和性能。希望这些内容能够对广大 SwiftUI 开发者在实际开发中有所帮助,打造出更加优秀的 iOS 应用。