Swift

Swift - 키보드 올라옴 감지(NotificationCenter, Observer 사용)

iosos 2023. 8. 11. 19:04

Notification

- 이벤트 자체

- 이벤트나 데이터의 특별한 형태로, 이름을 가지는 이벤트를 나타내는 클래스

- 앱 내에서 어떤 사건이 발생했음을 나타내며, 이를 통해 관련된 정보 전달 가능

- 주로 Notification.Name으로 식별 

 

 

NotificationCenter

- 이벤트를 관리하고 전달하는 중간 매개체 

- 앱 내에서 발생하는 이벤트들을 관리하고 이벤트를 발송하거나 수신하는 기능 제공 

- 이벤트가 발생하면 NotificationCenter는 해당 이벤트를 관심있는 모든 옵저버에게 전달

- 이를 통해 다양한 객체 간에 이벤트를 공유하고 통신 가능 

- iOS 앱에서 다양한 컴포넌트나 모듈 간의 통신을 도와주는 중요한 도구

- 객체들 간의 강한 의존성 없이 효율적으로 데이터나 이벤트를 공유 가능

 

 

 

사용자 로그인 이벤트 예제

// Notification을 사용한 예제
let loggedInNotification = Notification(name: Notification.Name("UserLoggedIn"), object: nil, userInfo: ["userID": "exampleUserID"])
NotificationCenter.default.post(loggedInNotification)

- Notification 을 생성하고 NotificationCenter를 통해 이벤트 발송

 

// NotificationCenter를 사용한 예제
NotificationCenter.default.addObserver(self, selector: #selector(userLoggedIn(_:)), name: Notification.Name("UserLoggedIn"), object: nil)

- NotificationCenter에 옵저버를 등록하고, 이벤트가 발생할 때 호출될 메서드 지정 

 

 

 

Observer

- NotificationCenter를 통해 등록되는 이벤트 리스너

- 특정 이벤트가 발생했을 때 이를 감지하고 적절한 동작을 수행하는 역할 수행 

- 객체 간에 느슨한 결합을 유지하며, 이벤트 발생 시 필요한 객체만 이를 처리하도록 도움 

 


 

 

 

1. view 설정 

- 화면에 아이디를 입력 받는 UI 구현

- baseView : 전체 화면을 커버하는 뷰

- titleLb : "아이디 입력" 텍스트를 보여주는 레이블

- textfield : 아이디를 입력하는 텍스트 필드

- bottomButton : 아래에 위치한 "다음" 버튼

- bottomBaseView : 화면 아래쪽을 커버하는 뷰

import UIKit

class KeyboardUpViewController: UIViewController {
    
    lazy var baseView : UIView = {
        var view = UIView()
        view.translatesAutoresizingMaskIntoConstraints = false
        
        return view
    }()
    
    lazy var titleLb : UILabel = {
        let label = UILabel()
        label.translatesAutoresizingMaskIntoConstraints = false
        label.text = "아이디 입력"
        
        return label
    }()
    
    lazy var textfield : UITextField = {
        var view = UITextField()
        view.translatesAutoresizingMaskIntoConstraints = false
        view.placeholder = "아이디를 입력해주세요. "
        
        
        return view
    }()
    
    lazy var bottomButton : UIButton = {
        let button = UIButton(type: .system)
        button.translatesAutoresizingMaskIntoConstraints = false
        button.setTitle("다음", for: .normal)
        button.backgroundColor = .blue
        button.setTitleColor(.white, for: .normal)
        button.titleLabel?.font = UIFont.systemFont(ofSize: 18, weight: .bold)
        return button
    }()
    
    lazy var bottomBaseView : UIView = {
        var view = UIView()
        view.translatesAutoresizingMaskIntoConstraints = false
        view.backgroundColor = .black
        return view
    }()

    override func viewDidLoad() {
        super.viewDidLoad()

        title = "아이디 설정"
//        view.backgroundColor = .red
        
        view.addSubview(baseView)
        baseView.addSubview(titleLb)
        baseView.addSubview(textfield)
        baseView.addSubview(bottomButton)
        view.addSubview(bottomBaseView)
        
        applyConstraints()
    }
    
    fileprivate func applyConstraints(){
        let baseViewConstraints = [
            baseView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
            baseView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
            baseView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
            baseView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor)
        ]
        
        let titleLbConstraints = [
            titleLb.leadingAnchor.constraint(equalTo: baseView.leadingAnchor, constant: 30),
            titleLb.topAnchor.constraint(equalTo: baseView.topAnchor, constant: 30)
        ]
        
        let textfieldConstraints = [
            textfield.leadingAnchor.constraint(equalTo: baseView.leadingAnchor, constant: 30),
            textfield.topAnchor.constraint(equalTo: titleLb.bottomAnchor, constant: 20)
        ]
        
        let bottomButtonConstraints = [
            bottomButton.leadingAnchor.constraint(equalTo: baseView.leadingAnchor),
            bottomButton.trailingAnchor.constraint(equalTo: baseView.trailingAnchor),
            bottomButton.bottomAnchor.constraint(equalTo: baseView.bottomAnchor),
            bottomButton.heightAnchor.constraint(equalToConstant: 50)
        ]
        
        let bottomBaseViewContraints = [
            bottomBaseView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
            bottomBaseView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
            bottomBaseView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
            bottomBaseView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
        ]
        
        NSLayoutConstraint.activate(baseViewConstraints)
        NSLayoutConstraint.activate(titleLbConstraints)
        NSLayoutConstraint.activate(textfieldConstraints)
        NSLayoutConstraint.activate(bottomButtonConstraints)
        NSLayoutConstraint.activate(bottomBaseViewContraints)
    }
    


}

→ 키보드가 올라왔을 때 'bottomButton' 이 사라짐 

- 이 문제를 해결하기 위해 NotificationCenter를 이용하여 키보드가 올라왔을 때의 이벤트 설정

 

 

 

 

 

 

2. keyboard의 움직임을 감지하는 Notification을 Observer 패턴으로 구독

// notification 추가하는 메서드
    func addKeyboardNotifications(){
        
        // 키보드가 나타날 때 앱에게 알리는 메서드 추가
        NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(_:)), name: UIResponder.keyboardWillShowNotification, object: nil)
        
        // 키보드가 사라질 때 앱에게 알리는 메서드 추가
        NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(_:)), name: UIResponder.keyboardWillHideNotification, object: nil)
    }
    
    // notification 제거하는 메서드
    func removeKeyboardNotifications(){
        
        // 키보드가 나타날 때 앱에게 알리는 메서드 제거
        NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillShowNotification, object: nil)
        
        // 키보드가 사라질 때 앱에게 알리는 메서드 제거
        NotificationCenter.default.removeObserver(self, name:
                                                    
                                                    UIResponder.keyboardWillHideNotification, object: nil)
    }
    
    // 키보드가 나타날 때 코드
    @objc func keyboardWillShow(_ noti : NSNotification){
        
    }
    
    // 키보드가 사라졌을 때 코드
    @objc func keyboardWillHide(_ noti : NSNotification){
        
    }

'NotificationCenter.default.addObserver( ... )' 

- NotificationCenter를 사용하여 키보드의 올라옴 이벤트를 감지할 수 있는 옵저버를 등록

- 'keyboardWillShowNotification' 이벤트가 발생할 때 'keyboardWillShow(_:))' 메서드를 호출하도록 설정 

 

'NotificationCenter.default.removeObserver( ... )'

- 등록된 옵저버 제거

- 옵저러를 등록했던 메서드와 동일한 이벤트와 객체에 대한 정보를 사용하여 제거 

 

 

 

 

3. keyboard 움직임을 감지하는 코드 작성

- 키보드가 올라온다면 bottomButton의 bottomAnchor.Constraints가 키보드 height 만큼 감소해야 함 

키보드 활성화 : bottomButton.bottomAnchor.constraint.constant = -keyboardHeight

키보드 비활성화 : bottomButton.bottomAnchor.constraint.constant = 0

 

 

 

 1) bottomButton의 bottomAnchor를 유동적으로 설정하기 위해 NSLayoutConstraint 타입의 변수 생성

var bottomButtonConstraint : NSLayoutConstraint?

 

 

 

2) bottomAnchor를 따로 빼내어 bottomButtonConstraint를 초기화하고 isActive로 활성화 

let bottomButtonConstraints = [
            bottomButton.leadingAnchor.constraint(equalTo: baseView.leadingAnchor),
            bottomButton.trailingAnchor.constraint(equalTo: baseView.trailingAnchor),
            bottomButton.heightAnchor.constraint(equalToConstant: 50)
        ]
        
        // bottomAnchor를 따로 빼어 설정
        bottomButtonConstraint = bottomButton.bottomAnchor.constraint(equalTo: baseView.bottomAnchor)
        bottomButtonConstraint?.isActive = true

 

 

 

3) 키보드 높이 구해서 bottomAnchor 설정 

// 키보드가 나타날 때 코드
    @objc func keyboardWillShow(_ noti : NSNotification){
        
        // 키보드의 높이만큼 화면을 올려줌
        if let keyboardFrame : NSValue = noti.userInfo? [UIResponder.keyboardFrameEndUserInfoKey] as? NSValue{
            let keyboardRectangle = keyboardFrame.cgRectValue
            let keyboardHeight = keyboardRectangle.height
            
            // bottomBaseView의 높이를 올려줌
            // 노치 디자인인 경우 safe area를 계산함
            if #available(iOS 11.0, *){
                let bottomInset = UIApplication.shared.windows.first(where: { $0.isKeyWindow })?.safeAreaInsets.bottom ?? 0
                let adjustedKeyboardHeight = keyboardHeight - bottomInset
                
                
                //bottomBaseView의 높이를 올림
                bottomButtonConstraint?.constant = -adjustedKeyboardHeight
            } else {
                
                // 노치 디자인이 없는 경우에는 원래대로 계산
                bottomButtonConstraint?.constant = -keyboardHeight
            }
            
            // 화면 업데이트
            view.layoutIfNeeded()
        }
        
    }
    
    // 키보드가 사라졌을 때 코드
    @objc func keyboardWillHide(_ noti : NSNotification){
        
        // 키보드의 높이만큼 화면을 내려줌
        bottomButtonConstraint?.constant = 0
        view.layoutIfNeeded()
        
    }

- keyboardFrame : NSValue 타입으로 키보드 프레임 정보를 저장할 변수 

- noti.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] : 알림의 userInfo에서 키보드 프레임 정보를 가져옴

- keyboardFrame.cgRectValue : keyboardRectangle에 키보드 CGRect 값 할당하여 높이 구함 

 

- #available(iOS 11.0, *) : iOS 11.0 이상에서만 해당 블록이 실행되도록 하는 역할 

- UIApplication.shared.window : 현재 앱의 윈도우 목록을 가져옴 

- first(where : {$0.isKeyWindow}) : 키보드가 표시된 상태의 윈도우를 가져옴 

- 해당 윈도우의 safeAreaInsets속성을 사용하여 SafeArea 여백 정보를 가져옴 

- adjustedKeyboardHeight : 키보드 높이에서 아래쪽 여백을 빼서 실제로 뷰를 위로 올려야 하는 높이 계산 후 constant 값 지정 

 

 

 

 

4. 키보드 내려감 설정

    // 화면 터치하면 키보드 내려감
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        view.endEditing(true)
    }

 

 

 


전체 코드)

//
//  KeyboardUpViewController.swift
//  PracticeSwift
//
//  Created by 이수현 on 2023/08/10.
//

import UIKit

class KeyboardUpViewController: UIViewController {
    
    // 키보드 올라올 때 bottomButton의 bottomAnchor 설정을 위해 사용
    var bottomButtonConstraint : NSLayoutConstraint?
    
    lazy var baseView : UIView = {
        var view = UIView()
        view.translatesAutoresizingMaskIntoConstraints = false
        
        return view
    }()
    
    lazy var titleLb : UILabel = {
        let label = UILabel()
        label.translatesAutoresizingMaskIntoConstraints = false
        label.text = "아이디 입력"
        
        return label
    }()
    
    lazy var textfield : UITextField = {
        var view = UITextField()
        view.translatesAutoresizingMaskIntoConstraints = false
        view.placeholder = "아이디를 입력해주세요. "
        
        
        return view
    }()
    
    lazy var bottomButton : UIButton = {
        let button = UIButton(type: .system)
        button.translatesAutoresizingMaskIntoConstraints = false
        button.setTitle("다음", for: .normal)
        button.backgroundColor = .blue
        button.setTitleColor(.white, for: .normal)
        button.titleLabel?.font = UIFont.systemFont(ofSize: 18, weight: .bold)
        return button
    }()
    
    lazy var bottomBaseView : UIView = {
        var view = UIView()
        view.translatesAutoresizingMaskIntoConstraints = false
        view.backgroundColor = .black
        return view
    }()

 
    
    override func viewDidLoad() {
        super.viewDidLoad()

        title = "아이디 설정"
//        view.backgroundColor = .red
        
        view.addSubview(baseView)
        baseView.addSubview(titleLb)
        baseView.addSubview(textfield)
        baseView.addSubview(bottomButton)
        view.addSubview(bottomBaseView)
        
        applyConstraints()
    }
    
    
    
    override func viewWillAppear(_ animated: Bool) {
        addKeyboardNotifications()
    }
    
    override func viewWillDisappear(_ animated: Bool) {
        removeKeyboardNotifications()
    }
    
    
    
    // notification 추가하는 메서드
    func addKeyboardNotifications(){
        
        // 키보드가 나타날 때 앱에게 알리는 메서드 추가
        NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(_:)), name: UIResponder.keyboardWillShowNotification, object: nil)
        
        // 키보드가 사라질 때 앱에게 알리는 메서드 추가
        NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(_:)), name: UIResponder.keyboardWillHideNotification, object: nil)
    }
    
    // notification 제거하는 메서드
    func removeKeyboardNotifications(){
        
        // 키보드가 나타날 때 앱에게 알리는 메서드 제거
        NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillShowNotification, object: nil)
        
        // 키보드가 사라질 때 앱에게 알리는 메서드 제거
        NotificationCenter.default.removeObserver(self, name:
                                                    
                                                    UIResponder.keyboardWillHideNotification, object: nil)
    }
    

    
    // 키보드가 나타날 때 코드
    @objc func keyboardWillShow(_ noti : NSNotification){
        
        // 키보드의 높이만큼 화면을 올려줌
        if let keyboardFrame : NSValue = noti.userInfo? [UIResponder.keyboardFrameEndUserInfoKey] as? NSValue{
            let keyboardRectangle = keyboardFrame.cgRectValue
            let keyboardHeight = keyboardRectangle.height
            
            // bottomBaseView의 높이를 올려줌
            // 노치 디자인인 경우 safe area를 계산함
            if #available(iOS 11.0, *){
                let bottomInset = UIApplication.shared.windows.first(where: { $0.isKeyWindow })?.safeAreaInsets.bottom ?? 0
                let adjustedKeyboardHeight = keyboardHeight - bottomInset
                
                
                //bottomBaseView의 높이를 올림
                bottomButtonConstraint?.constant = -adjustedKeyboardHeight
            } else {
                
                // 노치 디자인이 없는 경우에는 원래대로 계산
                bottomButtonConstraint?.constant = -keyboardHeight
            }
            view.layoutIfNeeded()
        }
        
    }
    
    // 키보드가 사라졌을 때 코드
    @objc func keyboardWillHide(_ noti : NSNotification){
        
        // 키보드의 높이만큼 화면을 내려줌
        bottomButtonConstraint?.constant = 0
        view.layoutIfNeeded()
        
    }
    
    
    // 화면 터치하면 키보드 내려감
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        view.endEditing(true)
    }

    
    // constarints 설정
    fileprivate func applyConstraints(){
        let baseViewConstraints = [
            baseView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
            baseView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
            baseView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
            baseView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor)
        ]
        
        let titleLbConstraints = [
            titleLb.leadingAnchor.constraint(equalTo: baseView.leadingAnchor, constant: 30),
            titleLb.topAnchor.constraint(equalTo: baseView.topAnchor, constant: 30)
        ]
        
        let textfieldConstraints = [
            textfield.leadingAnchor.constraint(equalTo: baseView.leadingAnchor, constant: 30),
            textfield.topAnchor.constraint(equalTo: titleLb.bottomAnchor, constant: 20)
        ]
        
        let bottomButtonConstraints = [
            bottomButton.leadingAnchor.constraint(equalTo: baseView.leadingAnchor),
            bottomButton.trailingAnchor.constraint(equalTo: baseView.trailingAnchor),
            bottomButton.heightAnchor.constraint(equalToConstant: 50)
        ]
        bottomButtonConstraint = bottomButton.bottomAnchor.constraint(equalTo: baseView.bottomAnchor)
        bottomButtonConstraint?.isActive = true
        
        let bottomBaseViewContraints = [
            bottomBaseView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
            bottomBaseView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
            bottomBaseView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
            bottomBaseView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
        ]
        
        NSLayoutConstraint.activate(baseViewConstraints)
        NSLayoutConstraint.activate(titleLbConstraints)
        NSLayoutConstraint.activate(textfieldConstraints)
        NSLayoutConstraint.activate(bottomButtonConstraints)
        NSLayoutConstraint.activate(bottomBaseViewContraints)
    }
}

 

 

 

 

 

참고)

https://ohwhatisthis.tistory.com/27 

'Swift' 카테고리의 다른 글

Swift - DispatchQueue  (0) 2023.08.22
Swift - UserDefaults  (0) 2023.08.16
Swift - 프로토콜 (Protocol)  (0) 2023.08.06
Swift 사진 설정하기 (UIImagePickerController 사용)  (0) 2023.08.05
Swift - UIKit, SnapKit 비교  (0) 2023.07.23