문제점
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)
}
}