You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI中keyboardWillShowNotification重复触发两次的问题求助

解决SwiftUI中键盘通知keyboardWillShow触发两次的问题

问题原因

你的代码存在三个核心问题导致通知重复触发:

  1. 观察者实例重复创建:使用@ObservedObject声明KeyboardObserver,当MovingTextfield视图因状态更新重新渲染时,会生成新的KeyboardObserver实例。
  2. 重复注册通知:onAppear可能被多次调用,每次调用都会执行setNotification添加新的观察者,同一个键盘通知会被多个观察者实例接收,导致keyboardWillShow触发多次。
  3. 未清理旧观察者:没有在对象销毁时移除观察者,旧实例的订阅未被取消,也会导致重复响应通知。

修复方案

1. 用@StateObject替代@ObservedObject

@StateObject会确保观察者实例在视图整个生命周期内只初始化一次,避免重复创建。

2. 改用Combine方式监听通知(推荐)

Combine的订阅会自动管理生命周期,无需手动移除观察者,更符合SwiftUI的响应式编程风格。

修改后的代码

KeyboardObserver类

import SwiftUI
import Combine

class KeyboardObserver: ObservableObject {
    @Published var activeState: Bool = false
    private var cancellables = Set<AnyCancellable>()
    
    init() {
        // 监听键盘弹出通知
        NotificationCenter.default.publisher(for: UIResponder.keyboardWillShowNotification)
            .sink { [weak self] _ in
                guard let self = self else { return }
                print("before appear state: \(self.activeState)")
                self.activeState = true
                print("after appear state: \(self.activeState)")
            }
            .store(in: &cancellables)
        
        // 监听键盘收起通知
        NotificationCenter.default.publisher(for: UIResponder.keyboardWillHideNotification)
            .sink { [weak self] _ in
                guard let self = self else { return }
                self.activeState = false
                print("disappear state: \(self.activeState)")
            }
            .store(in: &cancellables)
    }
}

MovingTextfield视图

struct MovingTextfield: View {
    @State var active: Bool = false
    @StateObject var observer = KeyboardObserver() // 替换为@StateObject
    @Binding var keywordValue: String
    
    var body: some View {
        VStack{
            Text("\(active)")
            Text("\(observer.activeState)")
            
            HStack{
                Spacer()
                TextField("\(Text("text input").font(.title))", text: $keywordValue)
                    .foregroundStyle(.black).font(.title)
                    .multilineTextAlignment(.center)
                    .disableAutocorrection(true)
                    
                Spacer()
            }
        }
        // 移除原onAppear中的setNotification调用
    }
}

ContentView保持不变

import SwiftUI

struct ContentView: View {
    @State var keywordValue = "ABCDEFG"
    var body: some View {
        VStack {
            Image(systemName: "globe")
                .imageScale(.large)
                .foregroundStyle(.tint)
            Text("Hello, world!")
            MovingTextfield(keywordValue: $keywordValue)
        }
        .padding()
    }
}

#Preview {
    ContentView()
}

额外说明

如果坚持使用selector方式监听通知,需要在KeyboardObserver的deinit方法中移除观察者,同时确保setNotification只被调用一次:

class KeyboardObserver: ObservableObject {
    @Published var activeState: Bool = false
    
    init() {
        setNotification()
    }
    
    deinit {
        NotificationCenter.default.removeObserver(self)
    }
    
    func setNotification(){
        NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object:nil)
        NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil)
    }
    
    @objc func keyboardWillShow(notification: Notification){
        print("before appear state: \(activeState)")
        activeState = true
        print("after appear state: \(activeState)")
    }

    @objc func keyboardWillHide(notification: Notification){
        activeState = false
        print("disappear state: \(activeState)")
    }
}

内容的提问来源于stack exchange,提问作者rainbowfart

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 04:07:08