음.. 제목 정하기가 참 힘드네요. UITextField는 보통 로그인과 회원가입 시 유저에게 문자열을 입력받기 위해서 많이 사용합니다.
로그인 페이지에 대해서 이런저런 고민이 있어 pinterest를 검색하던 와중에 아래의 UI 컨셉을 발견했습니다.
부연 설명을 하자면,
UITextField에 특정값 입력을 유도하는 placeholder는 입력이 시작되면 사라지게 되어 있습니다. 이 때 위 화면과 같이 타이틀이 별도로 없는 UI구성일 때는 좀 애매한 경우가 생기기도 하는데 재치있게 풀었더군요. 그래서 구현을 해봤습니다! (사실 다 아시는 건데 기록은 남겨야겠기에..)
아래 영상은 위 컨셉과 유사하게 컴포넌트를 적용한 화면입니다.
똑같진 않지만 비슷한가요…? 원리는 UITextField를 상속받은 클래스에 UILabel을 임의로 하나 더 addSubview 합니다.
사실 가장 효율적인 방법은 placeholder를 보여주는 view를 조작하는 것인데 쉽게하기로 맘먹고 UILabel을 배치했습니다.
이 코드에서 가장 중요한 포인트는 changeStatus입니다. enum으로 정의된 status에 따라 UILabel의 animation이 수행되도록 했습니다.
!! 아래 노출되는 코드들은 Swift 2.0 기준입니다.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// UTTextField.swift | |
// YFGAddressBook | |
// | |
// Created by KimYoonBong on 2015. 8. 28.. | |
// Copyright © 2015년 YMP. All rights reserved. | |
// | |
import UIKit | |
public enum UTTextFieldStatus : Int { | |
case Normal | |
case Input | |
} | |
class UTTextField: UITextField { | |
var upperLabel: UILabel! | |
override func awakeFromNib() { | |
// 클리어버튼의 색상변경하기 | |
let clearButton = self.valueForKey("_clearButton") as! UIButton | |
clearButton.tintColor = UIColor.whiteColor() | |
let currentImage = clearButton.currentImage | |
let renderedImage = currentImage?.imageWithRenderingMode(UIImageRenderingMode.AlwaysTemplate) | |
clearButton.setImage(renderedImage, forState: UIControlState.Normal) | |
// UITextField의 placeholder가 움직이는 것처럼 보이게 할 UILabel 정의 | |
self.upperLabel = UILabel(frame: CGRectZero) | |
self.upperLabel.backgroundColor = UIColor.clearColor() | |
self.upperLabel.textColor = UIColor(red: 253.0 / 255.0, green: 224.0 / 255.0, blue: 48.0 / 255.0, alpha: 1.0) | |
self.upperLabel.text = self.placeholder | |
self.upperLabel.font = UIFont.systemFontOfSize(10.0) | |
self.upperLabel.translatesAutoresizingMaskIntoConstraints = false | |
self.upperLabel.alpha = 0.0 | |
self.addSubview(self.upperLabel) | |
// 위 UILabel의 autolayout 정의 | |
let topCon = NSLayoutConstraint(item: self.upperLabel, attribute: .Top, relatedBy: .Equal, toItem: self, attribute: .Top, multiplier: 1.0, constant: 0.0) | |
let widthCon = NSLayoutConstraint(item: self.upperLabel, attribute: .Width, relatedBy: .Equal, toItem: self, attribute: .Width, multiplier: 1.0, constant: 0.0) | |
let heightCon = NSLayoutConstraint(item: self.upperLabel, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: 50.0) | |
let leadingCon = NSLayoutConstraint(item: self.upperLabel, attribute: .Leading, relatedBy: .Equal, toItem: self, attribute: .Leading, multiplier: 1.0, constant: 5.0) | |
self.upperLabel.addConstraint(heightCon) | |
self.addConstraint(leadingCon) | |
self.addConstraint(topCon) | |
self.addConstraint(widthCon) | |
// placeholder 색상 변경 | |
let placeholderColor = UIColor(white: 200.0 / 255.0, alpha: 0.8) | |
self.attributedPlaceholder = NSAttributedString(string:self.placeholder!, attributes: [NSForegroundColorAttributeName : placeholderColor]) | |
} | |
override func drawRect(rect: CGRect) { | |
// UITextField 하단의 밑줄 그리기 | |
let context = UIGraphicsGetCurrentContext() | |
CGContextSetLineWidth(context, 1.0) | |
let colorSpace = CGColorSpaceCreateDeviceRGB() | |
let components: [CGFloat] = [255.0 / 255.0, 255.0 / 255.0, 255.0 / 255.0, 0.5] | |
let color = CGColorCreate(colorSpace, components) | |
CGContextSetStrokeColorWithColor(context, color) | |
CGContextMoveToPoint(context, 0.0, CGFloat(CGRectGetHeight(rect) – 1.0)) | |
CGContextAddLineToPoint(context, CGRectGetWidth(rect), CGFloat(CGRectGetHeight(rect) – 1.0)) | |
CGContextStrokePath(context) | |
} | |
// status에 따라 animation 적용하기 | |
func changeStatus(status: UTTextFieldStatus) { | |
UIView.animateWithDuration(0.25, delay: 0.0, options: UIViewAnimationOptions.AllowUserInteraction, animations: { () -> Void in | |
switch status { | |
case .Normal: | |
self.upperLabel.alpha = 0.0 | |
self.upperLabel.transform = CGAffineTransformIdentity | |
break | |
case .Input: | |
self.upperLabel.alpha = 1.0 | |
self.upperLabel.transform = CGAffineTransformMakeTranslation(0.0, -14.0) | |
break | |
} | |
}) { (finish) -> Void in | |
} | |
} | |
// placeholder 위치 조정 | |
override func textRectForBounds(bounds: CGRect) -> CGRect { | |
return CGRectMake(CGRectGetMinX(bounds) + 5.0, CGRectGetMinY(bounds) + 8.0, CGRectGetWidth(bounds) – 20.0, CGRectGetHeight(bounds)) | |
} | |
// text 위치 조정 | |
override func editingRectForBounds(bounds: CGRect) -> CGRect { | |
return CGRectMake(CGRectGetMinX(bounds) + 5.0, CGRectGetMinY(bounds) + 8.0, CGRectGetWidth(bounds) – 20.0, CGRectGetHeight(bounds)) | |
} | |
// 클리어버튼의 위치 조정 | |
override func clearButtonRectForBounds(bounds: CGRect) -> CGRect { | |
var rect = super.clearButtonRectForBounds(bounds) | |
rect.origin.y = rect.origin.y + 8.0 | |
return rect | |
} | |
} |
그 다음은 UITextField delegate의 각 이벤트 메소드에 상황에 따른 status 변경을 합니다. 위 코드에서 정의한 changeStatus를 적절하게 호출합니다.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
func textFieldDidBeginEditing(textField: UITextField) { | |
self.showMessageToUser(nil) | |
} | |
func textFieldDidEndEditing(textField: UITextField) { | |
if textField.text?.characters.count <= 0 { | |
if let hasLabel = textField as? UTTextField { | |
hasLabel.changeStatus(.Normal) | |
} | |
} | |
} | |
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool { | |
if string.characters.count == 0 { | |
if textField.text?.characters.count <= 1 { | |
if let hasLabel = textField as? UTTextField { | |
hasLabel.changeStatus(.Normal) | |
} | |
} | |
} | |
else { | |
if let hasLabel = textField as? UTTextField { | |
hasLabel.changeStatus(.Input) | |
} | |
} | |
return true | |
} | |
func textFieldShouldReturn(textField: UITextField) -> Bool { | |
if textField.isEqual(self.emailTextField) { | |
self.passwordTextField.becomeFirstResponder() | |
} | |
else { | |
textField.resignFirstResponder() | |
} | |
return false | |
} | |
func textFieldShouldClear(textField: UITextField) -> Bool { | |
if let hasLabel = textField as? UTTextField { | |
hasLabel.changeStatus(.Normal) | |
} | |
return true | |
} |
비교적 쉽게 concept 디자인의 UITextField를 완성했습니다. (팁이나 될려나 몰겠네요..)