apple/SwiftUI, Combine

SwiftUI keyboard 이벤트 감지하기

lgvv 2024. 3. 6. 02:22

SwiftUI keyboard 이벤트 감지하기


앱 개발에 있어서 키보드 상태에 따라서 뷰의 다른 컴포넌트들의 높이가 조정되는 등 키보드와 관련해서는 꽤나 까다로움

 

UIKit을 사용한다면 

 - iOS 15 이상: keyboardLayoutGuide를 활용하여 레이아웃을 잡기

 - iOS 14 이하: 키보드의 상태에 따라 키보드의 높이를 계산해서 뷰의 위치를 조정

view.keyboardLayoutGuide

 

SwiftUI 사용

 - iOS 15 이상: @FocusState 활용

 - iOS 14 이하: NotificationCenter와 Combine을 활용

 

 

샘플코드

 

키보드 상태를 읽을 수 있도록 아래 코드를 작성

protocol KeyboardReadable {
    var keyboardPublisher: AnyPublisher<Bool, Never> { get }
}

extension KeyboardReadable {
    var keyboardPublisher: AnyPublisher<Bool, Never> {
        Publishers.Merge(
            NotificationCenter.default
                .publisher(for: UIResponder.keyboardWillShowNotification)
                .map { _ in true },
            
            NotificationCenter.default
                .publisher(for: UIResponder.keyboardWillHideNotification)
                .map { _ in false }
        )
        .eraseToAnyPublisher()
    }
}

 

 

위처럼 작성하고 아래처럼 뷰를 구현

 

import SwiftUI
import Combine

final class ContentViewModel: ObservableObject,
                              KeyboardReadable {
    
    enum Action {
        case shouldResignKeyboard
    }
    
    /// 검색어
    @Published var searchText: String = ""
    /// 키보드가 열려있는지 여부
    @Published var isShowingKeyboard: Bool = false
    
    init() {
        bind()
    }
    
    private var cancellables = Set<AnyCancellable>()
    
    private func bind() {
        keyboardPublisher
            .removeDuplicates()
            .sink { [weak self] isShowing in
                print("\(isShowing)")
                guard let self else { return }
                self.isShowingKeyboard = isShowing
            }.store(in: &cancellables)
    }
    
    func send(action: Action) {
        switch action {
        case .shouldResignKeyboard:
            UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder),
                                            to: nil, from: nil, for: nil)
        }
    }
}

struct ContentView: View {
    @StateObject var viewModel = ContentViewModel()
    
    var body: some View {
        VStack {
            Text(
                viewModel.isShowingKeyboard
                ? "키보드가 올라와 있어요."
                : "키보드가 내려간 상태에요."
            )
            
            TextField("검색어를 입력하세요.", text: $viewModel.searchText)
                .autocorrectionDisabled()
                .textFieldStyle(.roundedBorder)
            
            Button {
                viewModel.send(action: .shouldResignKeyboard)
            } label: {
                Text("키보드 강제로 내리는 버튼")
            }
            .buttonStyle(.bordered)
            
            Text("Hello, world!")
        }
        .padding()
    }
}

#Preview {
    ContentView()
}

 

 

키보드 뿐만 아니라 SwiftUI는 뷰가 많이 쪼개지기 때문에 해당 형태를 살짝 변형하여 Delegate Pattern 처럼 변형해 해당 뷰에서 발생하는 이벤트를 다른 뷰로 전달할 수 있다.

 

해당 코드 동작 영상

해당 코드 동작 영상