2017년 5월 1일 월요일

iOS & Swift: Keyboard로 사용자 입력할 때, ToolBar 위로 이동하기

이전 글인 Keyboard 이벤트를 이용해서, ToolBar를 이동하는 예제를 만들어 봅니다.

ViewController에서, 하단에 Toolbar가 있고, 사용자가 입력을 할 때, View를 위로 이동시켜서, 입력하는 내용이 화면에 표시되도록 하면, 어떤 내용을 입력하는지 알기 쉬운 UI가 됩니다.

ToolBar만 위로 이동해도 되지만, AutoLayout의 내용들을 다 무시하게 되므로, 전체 View를 Keyboard표시되는 것 만큼 위로 이동하게 만들어 봅니다.



 Keyboard 이벤트 설명 블로그에서 이벤트 등록과 이벤트 발생 때, 실행되는 함수를 등록하는 방법을 이미 설명했습니다.
이제 표시될 때, ToolBar를 위로 올려주고, 입력이 끝나면 밑으로 내려줘야 합니다. 그리고, 입력 중에 화면이 회전되면, 적당하게 ToolBar의 위치를 옮겨줘야 합니다.
위로 얼마나 올려야 되는지는

키보드 표시될 때와, 사라질 때 함수 호출하기.

    override func viewWillAppear(_ animated: Bool) {
        //화면이 표시될때, Keyboard표시에 대한 이벤트가 발생하면 호출되는 함수를 등록한다.
        NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillShow(_:)), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillHide(_:)), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
    }
    func keyboardWillShow(_ notification:NSNotification) {
        //키보드가 표시 될때, ToolBar의 위치를 올려준다.
        moveToolbarUp(with: notification)
    }
    func keyboardWillHide(_ notification:NSNotification) {
        //키보드가 사라질 때, ToolBar의 위치를 아래로 내려준다.
        moveToolbarDown(with: notification)
    }

Toolbar를 위로 올려주고, 아래로 내려주는 함수
    fileprivate func moveToolbarUp(with notification:NSNotification) {
        self.moveToolBar(isUp: true, with: notification)
    }
    fileprivate func moveToolbarDown(with notification:NSNotification) {
        self.moveToolBar(isUp: false, with: notification)
    }
    fileprivate func moveToolBar(isUp up:Bool, with notification:NSNotification) {
        if let userInfo = notification.userInfo {
            //let beginFrame = (userInfo[UIKeyboardFrameBeginUserInfoKey] as! NSValue).cgRectValue
            let endFrame = (userInfo[UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
            let duration = (userInfo[UIKeyboardAnimationDurationUserInfoKey] as! NSNumber).doubleValue
            let animationOptions = UIViewAnimationOptions(rawValue: (userInfo[UIKeyboardAnimationCurveUserInfoKey] as! NSNumber).uintValue)
            
            let frame = self.toolbar.frame
            let rect:CGRect = CGRect(x: frame.origin.x,
                                     y: frame.origin.y + endFrame.size.height * (up ? -1 : 1),
                                     width: frame.size.width,
                                     height: frame.size.height)
            UIView.animate(withDuration: duration,
                           delay: 0.0,
                           options: animationOptions,
                           animations: { () -> Void in
                            self.toolbar.frame = rect
            }, completion: nil)
            
        }else{
            //UserInfo가 없을 경우..
        }
    } 

ToolBar가 올라가 있는 상태에서 화면을 옆으로 돌리면, 툴바의 위치가 이상하게 되어 버리는 예외를 처리하기 위해서, 화면이 회전할 때, 키보드를 사라지게 해줍니다. 더 좋은 방법이 있겠지만, 이정도로 마무리..

    override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
        //화면이 옆으로 돌아갈 때, 호출되는 이벤트로, 이벤트 발생 시, 키보드를 아래로 내려주면 이동 후에 다시 선택하면 되도록 한다
        self.inputTextField.resignFirstResponder();
    }


2017년 3월 28일 화요일

iOS & Swift : Keyboard가 표시될 때, 사라질 때 이벤트와 그 키보드의 위치를 알아보자

지난 블로그에서는 Objective-C를 기준으로 정리하였는데, 이제는 Swift를 기준으로 정리한다.
(링크 [iOS] Keyboard가 표시될 때, 사라질 때 이벤트와 그 키보드의 위치는?)

일반적인 UIViewController 내에서는 표시할 위치를 이동시켜야, 키보드에 덮히지 않고, 사용자에게 표시할 수 있습니다.

1. 키보드가 화면에 나타나거나, 사라지는 Event는 무엇이고, 어떻게 알아낼까?

키보드가 표시될 때 전달되는 이벤트는...
  • NSNotification.Name.UIKeyboardWillShow : 키보드가 표시되기 전에 호출
  • NSNotification.Name.UIKeyboardDidShow : 키보드가 표시되고 난 후에 호출
  • NSNotification.Name.UIKeyboardWillHide : 키보드를 숨기기 전에 호출
  • NSNotification.Name.UIKeyboardDidHide : 키보드를 숨기고 난 후에 호출
  • NSNotification.Name.UIKeyboardWillChangeFrame : 키보드 모양이 바뀌기 전 (iOS 5 이상)
  • NSNotification.Name.UIKeyboardDidChangeFrame : 키보드 모양이 바뀐 후 (iOS 5 이상)
필요한 이벤트를 viewWillAppear 함수에서 NotificationCenter에 등록합니다.
Source Code
    override func viewWillAppear(_ animated: Bool) {
        //화면이 표시될때, Keyboard표시에 대한 이벤트가 발생하면 호출되는 함수를 등록한다.
        NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillShow(_:)), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardDidShow(_:)), name: NSNotification.Name.UIKeyboardDidShow, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillHide(_:)), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardDidHide(_:)), name: NSNotification.Name.UIKeyboardDidHide, object: nil)
    }

화면이 사라질 때, 등록한 것을 삭제해야 한다. 물론 삭제하면 더 이상 이벤트가 발생해도 해당 함수를 호출하지 않습니다.

SourceCode
    override func viewWillDisappear(_ animated: Bool) {
        //화면이 사라질 때, keyboard 표시에 대한 이벤트를 받지 않도록, 등록을 삭제한다.
        NotificationCenter.default.removeObserver(self)
    }


이벤트가 발생할 때, 호출하는 함수들을 정의한다. 해당 이름은 뭐든지 관계없으나 인자로 받는 것은 명시를 해줘야 합니다.

SourceCode
    //키보드가 표시 될 때 호출 되는 함수
    func keyboardWillShow(_ notification:NSNotification) {
        print(notification)
        info(name: "Keyboard Will beShown", with: notification)
        somethingDo(with: notification)
    }
    func keyboardDidShow(_ notification:NSNotification) {
        info(name: "Keyboard Was  Shown", with: notification)
    }
    
    //키보드가 사라질 때, 호출 되는 함수
    func keyboardWillHide(_ notification:NSNotification) {
        info(name: "Keyboard Will beHidden", with: notification)
        somethingDo(with: notification)
    }
    func keyboardDidHide(_ notification:NSNotification) {
        info(name: "Keyboard Was  Hidden", with: notification)
    }

자 이제, 키보드가 표시될 때 마다, 해당 함수들이 호출이 된다.
그럼 다음으로 넘어가자.

2. 호출된 키보드 이벤트 함수에서 키보드의 크기를 알아야, 다른 컴포넌트의 위치를 조정할 수 있다.

이벤트로 전달되는 NSNotification 클래스의 userInfo에서 해당 내용을 가져와야 알 수 있습니다.
키보드가 표시되는 Frame의 정보는 시작할 때와 표시가 다 되었을 때, 어디에 위치할지 알아낼 수 있습니다.
userInfo를 가져와서 description을 출력해 보면 아래와 같습니다.

NSConcreteNotification 0x610000242460 {name = UIKeyboardWillShowNotification; userInfo = {
    UIKeyboardAnimationCurveUserInfoKey = 7;
    UIKeyboardAnimationDurationUserInfoKey = "0.25";
    UIKeyboardBoundsUserInfoKey = "NSRect: {{0, 0}, {375, 258}}";
    UIKeyboardCenterBeginUserInfoKey = "NSPoint: {187.5, 796}";
    UIKeyboardCenterEndUserInfoKey = "NSPoint: {187.5, 538}";
    UIKeyboardFrameBeginUserInfoKey = "NSRect: {{0, 667}, {375, 258}}";
    UIKeyboardFrameEndUserInfoKey = "NSRect: {{0, 409}, {375, 258}}";
    UIKeyboardIsLocalUserInfoKey = 1;
}}

위에서 표시되는 각 UserInfoKey에 대해서 조금 더 자세히 봅시다.
  • UIKeyboardAnimationCurveUserInfoKey : 키보드가 표시될 때 Animation Curve
  • UIKeyboardAnimationDurationUserInfoKey : 키보드 표시되는 시간
  • UIKeyboardBoundsUserInfoKey : 키보드의 Bounds로 크기를 알 수 있는 bound
  • UIKeyboardCenterBeginUserInfoKey : 키보드 표시되기 시작할 때의 중심 Point
  • UIKeyboardCenterEndUserInfoKey : 키보드 표시된 후의 중심 Point
  • UIKeyboardFrameBeginUserInfoKey : 키보드가 표시되기 시작할 때의 frame
  • UIKeyboardFrameEndUserInfoKey : 키보드가 표시된 후의 Frame
  • UIKeyboardIsLocalUserInfoKey : 지금 표시되는 키보드가 Current App에 속한 것인지 True/False로 가지고 있음. iPad의 경우 여러 App이 화면에 표시될 수 있는데, 다른 App이 표시하는 키보드인지 내가 표시한 키보드인지 구분할 수 있음. 이것이 False이면 반응하면 안됨. (iOS 9 이상에서 지원)
아래 처럼 각 호출되는 함수에서 정보를 출력해 봅니다.

SourceCode

    fileprivate func info(name str:String, with notification:NSNotification) {
        if let userInfo = notification.userInfo {
            let frameBegin = (userInfo[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue ?? CGRect.zero
            let frameEnd = (userInfo[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue ?? CGRect.zero
            let curve = userInfo[UIKeyboardAnimationCurveUserInfoKey] as? NSNumber ?? NSNumber.init(value: 0)
            let duration = (userInfo[UIKeyboardAnimationDurationUserInfoKey] as! NSNumber ).doubleValue
            print("\(str) (\(Int(frameBegin.origin.x)),\(Int(frameBegin.origin.y)),\(Int(frameBegin.size.width)),\(Int(frameBegin.size.height))), (\(Int(frameEnd.origin.x)),\(Int(frameEnd.origin.y)),\(Int(frameEnd.size.width)),\(Int(frameEnd.size.height))) curve:\(curve), duration:\(duration)")
        }
    }



3. 결과로 나오는 값은 어떻게 될까요?


iPhone7
세로
Keyboard Will beShown (0,667,375,258), (0,409,375,258) curve:7, duration:0.25
Keyboard Was  Shown    (0,667,375,258), (0,409,375,258) curve:7, duration:0.25
Keyboard Will beHidden (0,409,375,258), (0,667,375,258) curve:7, duration:0.25
Keyboard Was  Hidden   (0,409,375,258), (0,667,375,258) curve:7, duration:0.25
가로
Keyboard Will beShown (0,375,667,194), (0,181,667,194) curve:7, duration:0.25
Keyboard Was  Shown    (0,375,667,194), (0,181,667,194) curve:7, duration:0.25
Keyboard Will beHidden (0,181,667,194), (0,375,667,194) curve:7, duration:0.25
Keyboard Was  Hidden   (0,181,667,194), (0,375,667,194) curve:7, duration:0.25

위 결과를 보시면, 표시되기 전에는 화면 아래 부분에 있다가 표시되면, 위로 올라오게 됩니다.
가로/세로 모드일때 따라서, 키보드의 위치와 크기가 달라집니다.

위 정보를 이용해서, UIViewController에서  ToolBar나 TextField가 키보드 아래에 있을 때, 키보드가 표시될 때 위로 이동시켜서 화면에 표시할 수 있고, 키보드가 사라질 때, 원래 위치로 이동할 수 있습니다.