본문 바로가기

Swift

View animation과 keyboard의 becomeFirstResponder 처리

문제점

Self.view 안에 여러 개의 view를 추가하여 마치 카드 넘기는 것처럼 처리를 하였다. view가 추가될 때 마다 왼쪽에서 slide in 하는데, 마침 해당 view에 textfield가 있었다.
그래서 textfield에 becomeFirstResponder()처리를 하였더니 view가 왼쪽에서 들어오는 animation 도중에 keyboard가 튀어올라오는 것이 아닌가? 문제는 키보드가 튀어나오면서 정작 보여져야 하는 textfield를 가리는 것이었다.
keyboard 이벤트를 등록하여 별 처리를 다 해보았지만 view animation 때문에 해당 이벤트 처리가 잘 안 되었다.
결국 해결 방법은 animation이 끝나고 나서 키보드가 나오게 하는 방법 밖에는 없다고 생각되었다.

Source code

Main Storyboard

UI를 소스에서 추가하였기 때문에 별다른 설정은 없다.


SettingsViewController.swift

여러 view 처리를 하는 클래스

class SettingsViewController: UIViewController {       
    var views: [UIView] = []
    let uidesign = UIDesign()
    let util = Util()
    var distribution = 0
    var keyboardYN = false
    var rectKeyboard: CGRect!

    var nameField = UITextField()

    override func viewDidLoad() {
        super.viewDidLoad()

        // keyboard 이벤트 등록
        self.performSelector(#selector(registerKeyboardEvent))

        setupUI()
    }

    override func viewDidDisappear(animated: Bool) {
        // keyboard 이벤트 등록 해제
        self.performSelector(#selector(unregisterKeyboardEvent))
    }

    // 텍스트필드말고 다른 곳 터치하면 키보드를 가리도록 한다.
    override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
        nameField.endEditing(true)
    }

    // 키보드가 떠오를 때 발생하는 이벤트 처리
    func keyboardWillShow(notification: NSNotification) {
        debugPrint("keyboard will show")
        keyboardYN = true
        let userInfo = notification.userInfo!
        var rectView = self.view.frame
        rectKeyboard = (userInfo[UIKeyboardFrameEndUserInfoKey] as! NSValue).CGRectValue()
        //rectKeyboard  = self.nameField.convertRect(rectKeyboard, fromView:nil)
        rectView.origin.y -= rectKeyboard.size.height

        UIView.animateWithDuration(5, animations: {
            self.view.frame = rectView
            self.view.layoutIfNeeded()
        })
    }

    // 키보드가 사라질 때 발생하는 이벤트 처리
    func keyboardWillHide(notification: NSNotification) {
        debugPrint("keyboard will hide")
        keyboardYN = false
        let userInfo = notification.userInfo!
        var rectView = self.view.frame
        rectKeyboard = (userInfo[UIKeyboardFrameEndUserInfoKey] as! NSValue).CGRectValue()
        //rectKeyboard  = self.nameField.convertRect(rectKeyboard, fromView:nil)
        rectView.origin.y += rectKeyboard.size.height

        UIView.animateWithDuration(1, animations: {
            self.view.frame = rectView
            self.view.layoutIfNeeded()
        })
    }

    func viewUpDownbyKeyboard() {
        if let rectkeyboard = rectKeyboard {

            var rectView = self.view.frame
            if keyboardYN {
                // 키보드가 보여지고 있다면
                rectView.origin.y -= rectkeyboard.size.height
            }
            else {
                // 키보드가 사라지고 있다면
                rectView.origin.y += rectkeyboard.size.height
            }
            UIView.animateWithDuration(1, animations: {
                self.view.frame = rectView
                self.view.layoutIfNeeded()
            })
        }
    }

    // 위 두 가지 키보드 이벤트를 이벤트로 등록
    func registerKeyboardEvent() {
        NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(keyboardWillShow), name: UIKeyboardWillShowNotification, object: nil)
        NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(keyboardWillHide), name: UIKeyboardWillHideNotification, object: nil)
    }

    // 위 두 가지 키보드 이벤트를 이벤트에서 해제
    func unregisterKeyboardEvent() {
        NSNotificationCenter.defaultCenter().removeObserver(self, name: UIKeyboardWillShowNotification, object: nil)
        NSNotificationCenter.defaultCenter().removeObserver(self, name: UIKeyboardWillHideNotification, object: nil)
    }

    func setupUI() {

        //Navi Bar
        self.title = "Math Avengers - Settings"
        self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: "이전 단계로", style: .Plain, target: self, action: #selector(self.leftBarButtonPressed))
        self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "다음 단계로", style: .Plain, target: self, action: #selector(self.nextButtonPressed))

        nextButtonPressed()
    }

    func leftBarButtonPressed() {
        let viewsIndex = views.count - 1
        switch viewsIndex {
        case 0:
            GlobalSettings.user = ""
            self.navigationController?.popToRootViewControllerAnimated(true)
            break
        default:
            backButtonPressed()
            break
        }
    }

    func nextButtonPressed() {
        let viewsIndex = views.count - 1
        switch viewsIndex {
        case 0:
            if let name = nameField.text {
                if name != "" {
                    GlobalSettings.user = name
                }
                else {
                    self.presentViewController(util.alert("앗!", message: "이름을 입력하지 않으셨네요~", ok: "네, 입력할게요", cancel: nil), animated: true, completion: nil)
                    nameField.becomeFirstResponder()
                    return
                }
            }
            else {
                self.presentViewController(util.alert("앗!!", message: "이름을 입력하지 않으셨네요~", ok: "네~ 입력할게요", cancel: nil), animated: true, completion: nil)
                nameField.becomeFirstResponder()
                return
            }
            break
        default:
            break
        }

        let nameView = UIView()
        views.append(nameView)
        uidesign.setViewLayout(nameView, color: nil)

        nameView.translatesAutoresizingMaskIntoConstraints = false
        super.view.addSubview(nameView)

        let viewsDictionary = ["nameView": nameView]
        self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-20-[nameView]-20-|",
            options: .AlignAllCenterX, metrics: nil, views: viewsDictionary))
        self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-\((self.navigationController?.navigationBar.frame.size.height)! + 40)-[nameView]-20-|", options: .AlignAllCenterY, metrics: nil, views: viewsDictionary))
        nameView.slideInFromLeft(1, completionDelegate: self)

        self.setComponent(self.views.count - 1)

        if views.count > 1 {
            views[views.count - 2].fadeOut()
        }
    }

    func backButtonPressed() {

        if views.count > 1 {
            views.last?.fadeOut()
            views.removeLast()
            views.last?.fadeIn()
        }

    }

    func setComponent(seq: Int) -> Void {
        switch seq {
        case 0:

            let headerImage = UIImageView(image: UIImage(named: "name"))
            headerImage.translatesAutoresizingMaskIntoConstraints = false
            headerImage.contentMode = .ScaleAspectFit

            let nameLabel = UILabel()
            //nameLabel.backgroundColor = UIColor.lightGrayColor()
            nameLabel.font = UIFont(name: "Verdana", size: 40)
            nameLabel.text = "이름을 적어주세요"
            nameLabel.textAlignment = .Center
            nameLabel.translatesAutoresizingMaskIntoConstraints = false
            nameLabel.heightAnchor.constraintEqualToConstant(200).active = true

            nameField.delegate = self
            nameField.font = UIFont(name: "Verdana", size: 40)
            nameField.textAlignment = .Center
            nameField.layer.cornerRadius = 10
            nameField.layer.borderColor = UIColor.blackColor().CGColor
            nameField.layer.borderWidth = 1
            nameField.translatesAutoresizingMaskIntoConstraints = false
            nameField.heightAnchor.constraintEqualToConstant(100).active = true

            let nextImage = UIImageView(image: UIImage(named: "next"))
            nextImage.translatesAutoresizingMaskIntoConstraints = false
            nextImage.contentMode = .ScaleAspectFit
            let nextPressed = UITapGestureRecognizer(target: self, action: #selector(nextButtonPressed))
            nextImage.userInteractionEnabled = true
            nextImage.addGestureRecognizer(nextPressed)


            let stackView = UIStackView()
            stackView.translatesAutoresizingMaskIntoConstraints = false
            stackView.axis = .Vertical
            stackView.distribution = .EqualCentering
            stackView.alignment = .Fill
            stackView.spacing = 20

            stackView.addArrangedSubview(headerImage)
            stackView.addArrangedSubview(nameLabel)
            stackView.addArrangedSubview(nameField)
            stackView.addArrangedSubview(nextImage)
            views[seq].addSubview(stackView)


            let viewsDictionary = ["stackView": stackView]
            views[seq].addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-20-[stackView]-20-|",
                options: .AlignAllCenterX, metrics: nil, views: viewsDictionary))
            views[seq].addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-20-[stackView]-20-|",
                options: .AlignAllCenterY, metrics: nil, views: viewsDictionary))

            //nameField.becomeFirstResponder()

            break
        default:
            break
        }
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }


    /*
    // MARK: - Navigation

    // In a storyboard-based application, you will often want to do a little preparation before navigation
    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
        // Get the new view controller using segue.destinationViewController.
        // Pass the selected object to the new view controller.
    }
    */

}

extension SettingsViewController: UITextFieldDelegate {

    func textFieldShouldReturn(textField: UITextField) -> Bool {
        textField.resignFirstResponder()
        self.nextButtonPressed()
        return true
    }
}

UIViewExtensions.switf

view를 slide in 하게 해주는 animation이 들어있는 소스이다.
처음에는 CATransaction 블럭이 없었는데 animation completion 처리를 하기 위해서 추가하였다.

extension UIView {
    // Name this function in a way that makes sense to you...
    // slideFromLeft, slideRight, slideLeftToRight, etc. are great alternative names
    func slideInFromLeft(duration: NSTimeInterval = 1.0, completionDelegate: AnyObject? = nil) {

        CATransaction.begin()

        // Create a CATransition animation
        let slideInFromLeftTransition = CATransition()

        // Set its callback delegate to the completionDelegate that was provided (if any)
        if let delegate: AnyObject = completionDelegate {
            slideInFromLeftTransition.delegate = delegate
            CATransaction.setCompletionBlock({
                debugPrint("slide in completed..")
                let settingsView = delegate as! SettingsViewController
                settingsView.nameField.becomeFirstResponder()
            })

        }

        // Customize the animation's properties
        slideInFromLeftTransition.type = kCATransitionPush
        slideInFromLeftTransition.subtype = kCATransitionFromLeft
        slideInFromLeftTransition.duration = duration
        slideInFromLeftTransition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
        slideInFromLeftTransition.fillMode = kCAFillModeRemoved

        // Add the animation to the View's layer
        self.layer.addAnimation(slideInFromLeftTransition, forKey: "slideInFromLeftTransition")


        CATransaction.commit()
    }

    func fadeIn() {
        // Move our fade out code from earlier
        UIView.animateWithDuration(1.0, delay: 0.0, options: UIViewAnimationOptions.CurveEaseIn, animations: {
            self.alpha = 1.0 // Instead of a specific instance of, say, birdTypeLabel, we simply set [thisInstance] (ie, self)'s alpha
            }, completion: nil)
    }

    func fadeOut() {
        UIView.animateWithDuration(1.0, delay: 0.0, options: UIViewAnimationOptions.CurveEaseOut, animations: {
            self.alpha = 0.0
            }, completion: nil)
    }
}