Review of Implement a Custom Control (1) - FoodTracker
์ด ์ฅ์์ ํด๋ณธ ๊ฒ๋ค
1. Custom source code ๋ฅผ ์์ฑํ๊ณ ๊ทธ๊ฒ์ stroyboard ๋ด์ ์์๊ณผ ์ฐ๊ฒฐํ๊ธฐ
2. Container view๋ก์ UIStackView ์ฌ์ฉํ๊ธฐ
3. ์ฝ๋๋ก view ๋ค์ ์์ฑํ๊ธฐ
4. ์ ๊ทผ์ฑ ์ ๋ณด๋ฅผ custom control ์ ์ถ๊ฐํ๊ธฐ
5. @IBInspectable, @IBDesignable ์ฌ์ฉํ์ฌ Interface Builder์์ custom view ๋ฅผ ๋์ฐ๊ณ , ์กฐ์ํ๊ธฐ
Create a Custom View
Container View ์ธ UIStackView ์์ 5๊ฐ์ UIButton ์ ๋ฃ์ด์ ๊ตฌํํฉ๋๋ค.
UIButton ์ ์๋ธํด๋์ฑํ์ฌ Custom control ์ ์ฌ์ฉํฉ๋๋ค.
์ด contol ์ ํตํด ์ฌ์ฉ์๋ meal ( ์์ ) ์ ํ๊ฐํฉ๋๋ค.
์ฌ์ฉ์๊ฐ ํน์ ๋ณ์ tap ํ๋ฉด, ํน์ ๋ณ์ ํฌํจํ ์ด์ ์ ๋ณ๊น์ง ๋ชจ๋ ๊ฒ์์์ผ๋ก ์ฑ์์ง๋๋ค.
์๋ฅผ ๋ค์ด ์ฌ์ฉ์๊ฐ ๊ฐ์ฅ ์ค๋ฅธ์ชฝ์ ๋ณ์ tap ํ๋ค๋ฉด, ๋ชจ๋ ๋ณ์ด ๊ฒ์์์ผ๋ก ์ฑ์์ง ๊ฒ์ ๋๋ค.
๊ทธ๋ฆฌ๊ณ ์ฑ์์ง ๋ณ ์ค ๊ฐ์ฅ ์ค๋ฅธ์ชฝ์ ๋ณ์ ๋ค์ tap ํ๋ฉด ๋ชจ๋ ๋ณ์ด ๋น์์ง๋๋ค.
์ฐ์ UIStackView๋ฅผ ์๋ธํด๋์ฑํ ์ปค์คํ ์ฝ๋๋ฅผ ๋ง๋ญ๋๋ค.
View ๋ฅผ ์์ฑํ๋ ๋ฐฉ๋ฒ์ ํฌ๊ฒ ๋ ๊ฐ์ง๊ฐ ์์ต๋๋ค.
1. View๋ฅผ ์ฝ๋๋ก ์ด๊ธฐํํ๊ธฐ
2. Stroyboard ์์ ๋ถ๋ฌ์ค๊ธฐ
`1 `์์๋ init(frame:) ๋ฉ์๋๋ก view๋ฅผ ์ด๊ธฐํ ํฉ๋๋ค.
`2 `์์๋ init?(coder:) ๋ฉ์๋๋ก storyboard์์ view๋ฅผ ๋ถ๋ฌ์ต๋๋ค.
Initializer ๋ ์ฌ์ฉํ ํด๋์ค์ ์ธ์คํด์ค๋ฅผ ์ค๋นํด์ฃผ๋ ๋ฉ์๋๋ผ๋ ๊ฒ์ ์๊ธฐํ์๊ธฐ ๋ฐ๋๋๋ค.
์ฌ๊ธฐ์ ์ค๋นํด์ค๋ค๋ ๊ฒ์ ์ธ์คํด์ค์ ๊ฐ ์์ฑ์ ์ด๊ธฐํํด์ฃผ๊ณ ๋ค๋ฅธ setup ๋ค์ ์ํํ๋ ๊ฒ์ ์๋ฏธํฉ๋๋ค.
์ฑ์ ์ค๊ณํ ๋, Interface Builder ๋ view ๋ฅผ canvas ์ ์ถ๊ฐํ ๋ ์ฝ๋๋ฅผ ์ด์ฉํด view๋ฅผ ์ธ์คํด์คํ ํฉ๋๋ค.
์๋ ๋จ๊ณ๋ฅผ ์งํํ๋ค๋ณด๋ฉด, view์ ์ฐ๊ฒฐ๋ ๊ด๋ จ ์ฝ๋๊ฐ ๋ฐ๋ก ์์ผ๋ฉด Inspector configuration ์๋ฐ๋ผ ์ ์ฉํจ์ ์ ์ ์์ต๋๋ค.
์ฑ์ ์คํํ ๋, ์ฑ์ stroyboard ์์ view๋ฅผ ๋ถ๋ฌ์ต๋๋ค.
๊ทธ๋ ๊ธฐ ๋๋ฌธ์ ์ ๋ Initialization ๋ชจ๋ ๊ตฌํํด์ผ ํฉ๋๋ค.
class RatingControl: UIStackView {
//MARK: Initialization
override init(frame: CGRect) {
super.init(frame: frame)
}
required init(coder: NSCoder) {
super.init(coder: coder)
}
}
NOTE
Swift๋ ํด๋์ค์ ์ด๋ ํ Initializer๊ฐ ์กด์ฌํ์ง ์์ ๋, ์๋์ผ๋ก super class ์ ๋ชจ๋ designated initializer ๋ฅผ ์์ํฉ๋๋ค.
๋ง์ฝ ํ ๊ฐ ์ด์์ initializer ๋ฅผ ์ง์ ๊ตฌํํ๋ค๋ฉด, ๋ ์ด์ super class ์ ๋ชจ๋ designated initializer ๋ค์ ์๋์ผ๋ก ์ ๊ณต๋์ง ์์ต๋๋ค.
๋ฐ๋ผ์ ์ด ๊ฒฝ์ฐ super class์ `required` initializer๊ฐ ์๋ค๋ฉด ๋ฐ๋์ ๊ตฌํํด์ผ ํฉ๋๋ค.
์ถ๊ฐ์ ์ผ๋ก sub class ๋ ๊ตฌํํ super class ์ `required` initializer ์์ `required` ํค์๋๋ฅผ ๋ฐ๋์ ๋ถ์ฌ์ผ ํฉ๋๋ค.
Display the Custom View
Story board ์์ Object Library ๋ก Horizontal Stack View ๋ฅผ ์ฌ์ ์ถ๊ฐ ํ ํ,
Identity Inspector๋ฅผ ํตํด ์์์ ์์ฑํ `RatingControl` Class ์ ์ฐ๊ฒฐํ์์ต๋๋ค.
Add Buttons to the View
์ฝ๋๋ก ์คํ ๋ทฐ์ ๋ฒํผ์ถ๊ฐํ๊ธฐ
์ ๋จ๊ณ์์, ์ ๋ `RatignControl` ์ด๋ผ๋ UIStackView ๋ฅผ ์๋ธํด๋์ฑํ Custom class ๋ฅผ ๋ง๋ค์์ต๋๋ค.
์ด์ ์ด View ์ ๋ฒํผ(UIButton) ์ ์ถ๊ฐํด์ผ ํฉ๋๋ค.
์์ ๋ ๊ฐ์ Initializer ์ค ์ด๋ค ๋ฉ์๋๊ฐ ํธ์ถ๋๋๋ผ๋ ๋ฒํผ์ด ์ถ๊ฐ๋์ด์ผ ํ ๊ฒ ์ ๋๋ค.
๋ฐ๋ผ์ private method๋ก ๋ฒํผ์ ์์ฑํ๋ ์ฝ๋๋ฅผ ์์ฑ ํ ๋ ๊ฐ์ Initializer์ ๋ชจ๋ ์ถ๊ฐํฉ๋๋ค.
Here, you are using one of the UIButton class’s convenience initializers. `UIButton()`
This initializer calls init(frame:) and passes in a zero-sized rectangle.
//MARK: Private Methods
private func setupButtons() {
// Create the button
let button = UIButton()
button.backgroundColor = .red
// Add constraints
/* 1 */
button.translatesAutoresizingMaskIntoConstraints = false
/* 2 */
button.heightAnchor.constraint(equalToConstant: 44.0).isActive = true
/* 3 */
button.widthAnchor.constraint(equalToConstant: 44.0).isActive = true
// Add the button to the stack
/* 4 */
addArrangedSubview(button)
}
/* 1 */ ์ ๋ฒํผ์ constraints ๊ฐ ์๋์ผ๋ก ์์ฑ๋๋ ๊ฒ์ ๋ง์ต๋๋ค.
view ๋ฅผ ์ฝ๋์์ ์ธ์คํด์คํ (Instantiate) ํ ๋, translatesAutoresizingMaskIntoConstraints ์ ๊ธฐ๋ณธ ๊ฐ์ true ์ ๋๋ค.
์ด๊ฒ์ layout engine ์ด view ์ ์์ฑ์ธ frame ๊ณผ autoresizingMask ์ ๊ธฐ๋ฐํด ์๋์ผ๋ก view์ ํฌ๊ธฐ์ ์์น๋ฅผ ์ค์ ํ๋ค๋ ๊ฒ์ ์๋ฏธํฉ๋๋ค.
๋ณดํต AutoLayout ์ ์ฌ์ฉํ๋ค๋ฉด, ์ด autogenerated constraints ๋ค ๋์ ์ง์ ์ค์ ํ ๊ฐ์ ์ฐ๊ณ ์ถ์ดํฉ๋๋ค.
๋ฐ๋ผ์ false ๋ก ์ค์ ํ์์ต๋๋ค.
Layout engine์ด ์๋์ผ๋ก ์์ฑํ constraints = autogenerated constraints
/* 2 */ ์ /* 3 */ ์์ heightAnchor ์ widthAnchor ๋ view์ ๋์ด์ ๋์ด๋ฅผ ์ค์ ํ๋ constraints ๋ฅผ ์์ฑํฉ๋๋ค.
1. Button์ heightAnchor ์ widthAnchor ์์ฑ์ layout anchor ๋ก์ ์ ๊ทผ์ ์ ๊ณตํฉ๋๋ค. ์ด layout anchor๋ฅผ ํตํ์ฌ constraints๋ฅผ ์์ฑํฉ๋๋ค. - ์ ์ฝ๋์ ๊ฒฝ์ฐ view ์ height ์ width๋ฅผ ๊ฐ๊ฐ ์ ์ํ๋ constraints ๋ฅผ ์์ฑํ์์ต๋๋ค.
2. Anchor์ constraint(equalToConstant:) ๋ฉ์๋๋ immutable ํ height ํน์ width ํฌ๊ธฐ๋ฅผ ๋ถ์ฌํ๋ constraint๋ฅผ ๋ฐํํฉ๋๋ค.
3. Constraints ์ isActive ์์ฑ์ ํตํด ํ์ฑ/๋นํ์ฑ์ ๊ฒฐ์ ํฉ๋๋ค. ๊ฐ์ด true๋ผ๋ฉด, ์์คํ ์ ๋ง๋ view ์ constraints ๋ฅผ ์ถ๊ฐํ๊ณ ํ์ฑํ ํฉ๋๋ค.
๊ฒฐ๊ณผ์ ์ผ๋ก /* 1 */ /* 2 */ /* 3 */ ์ ํตํด Button ์ ๊ณ ์ ๋ ํฌ๊ธฐ์ object๋ก ์ ์ํ๊ฒ ๋์์ต๋๋ค. ( 44 points x 44 points )
/* 4 */ ์์ addArrangedSubview( ) ๋ฉ์๋๋ RatingControl ์คํ๋ทฐ๊ฐ ๊ด๋ฆฌํ๋ view์ ๋ฆฌ์คํธ์ button ์ ๋ฃ๋ ์ฝ๋์ ๋๋ค.
์ด ๋ฉ์๋๋ฅผ ํตํด button์ RatingControl์ subview๊ฐ ๋๊ณ , RatingControl ์๊ฒ RatingControl ๋ด๋ถ์์ button ์ ์์น๋ฅผ ๊ด๋ฆฌํ contstraints๋ฅผ ์์ฑํ๋๋ก ์๋ ค์ค๋๋ค.
์ฝ๋๋ก ๋ฒํผ์ ์ก์ ์ถ๊ฐํ๊ธฐ
private func setUpButton() {
...
button.addTarget(self, action: #selector(self.ratingButtonTapped(button:)), for: .touchUpInside)
addArrangedSubView(button)
}
@objc func ratingButtonTapped(button: UIButton) {
//code
}
์ ์ฝ๋๋ target-action pattern ์ ์ฝ๋๋ก ๊ตฌํํ๋ ๋ฐฉ๋ฒ์ ๋๋ค.
addTarget(_, action:, for) ๋ฉ์๋๋ฅผ ์ด์ฉํฉ๋๋ค. button ์์ ์ด ๋ฉ์๋๋ฅผ ํตํด button์ touchUPInside ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ ๋๋ง๋ค ratingButtonTapped(button:) ๋ฉ์๋๊ฐ ์คํ๋๊ฒ ํฉ๋๋ค.
addTarget(_, action:, for) ๋ฉ์๋๋ฅผ ์์ธํ๊ฒ ๋ถ์ ํด๋ณด๋ฉด:
์ฒซ๋ฒ์งธ parameter ์๋ target ์ด ๋ค์ด๊ฐ๋๋ฐ, action์ ๊ตฌํํ ํด๋์ค์ ์ ๋ฌํด์ผํ๋ฏ๋ก `self ๊ฐ ๋๊ฒ ์ต๋๋ค.
๋๋ฒ์งธ parameter ์์ #selector ๋ ์ฃผ์ด์ง ๋ฉ์๋์ Selector ๊ฐ์ ๋ฐํํฉ๋๋ค.
Selector๋ ๋ฉ์๋๋ฅผ ์๋ณํ๋ ๋ถํฌ๋ช ํ ๊ฐ ์ ๋๋ค.
์ค๋๋ API๋ค์ ๋ฉ์๋๋ค์ ๋์ ์ผ๋ก ๋ถ๋ฅผ๋ selector๋ฅผ ์ฌ์ฉํ์์ต๋๋ค.
performSelector(_:) and addTarget(_:action:forControlEvents:) ๊ฐ ๊ทธ ์๊ฐ ๋๊ณ ์์ง ์ฌ์ฉํ๋ API์ ๋๋ค.
#selector(RatingControl.ratingButtonTapped(_:)) ๊ฐ ratingButtonTapped(_:) action method ์ selector ๊ฐ์ ๋ฐํํด ์ค๋๋ค.
์ธ๋ฒ์งธ parameter ์๋ UIControlEvents ํ์ ์ด ๋ค์ด๊ฐ๋๋ค. ์ด ํ์ ์ control ์ด ๋ฐ์ํ ์ ์๋ ์ด๋ฒคํธ์ ์ข ๋ฅ๋ฅผ ์ ์ํ๊ณ ์์ต๋๋ค.
์ ์ฝ๋์์๋ .touchUpInside ์ด๋ฒคํธ๋ฅผ parameter๋ก ์ ๋ฌํ์์ต๋๋ค.
๋ง์ง๋ง์ผ๋ก, Interface Builder ๋ก ์ก์ ์ ์ฐ๊ฒฐํ๊ณ ์์ง ์๊ธฐ ๋๋ฌธ์, IBAction attribute ๋ฅผ action ๋ฉ์๋ ์์ ์ ์ํ ํ์๊ฐ ์์ต๋๋ค. ๋ฉ์๋์ parameter ์ต์ ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
func doSomething()
/* ๋ฉ์๋๋ฅผ ํธ์ถํ control ์ธ์คํด์ค ์ ๋ฌ */
func doSomething(sender: UIButton)
/* control ์ธ์คํด์ค plus ๋ฐ์ํ ์ด๋ฒคํธ ์ข
๋ฅ ์ ๋ฌ */
func doSomething(sender: UIButton, forEvent event: UIEvent)
N O T E
๊ด๋ จ ๊ธ: woozzang.tistory.com/50
์ถ๊ฐ์ ์ผ๋ก `Touch Up Inside` ๋ ์ด๋ฒคํธ์ ์ข ๋ฅ๋ก์, ์ผ๋ฐ์ ์ผ๋ก ์๊ฐํ๋ ํญ ๋์์ ๋ฐ์ํ๋ ์ด๋ฒคํธ์ ๋๋ค.
์๋ฐํ ์ด์ผ๊ธฐํ๊ธฐ ์ํด `Touch Up Outside` ์ด๋ฒคํธ์ ๋น๊ตํด๋ณด๊ฒ ์ต๋๋ค.
์ฌ์ฉ์์ ๋์์ ์ผ๋จ Touch์ Up์ผ๋ก ๋ถ๋ฆฌํด์ ์๊ฐํด์ผ ํฉ๋๋ค.
๋ง ๊ทธ๋๋ก 1. (์๊ฐ๋ฝ์ผ๋ก) ํฐ์นํ๋ ๋์๊ณผ 2. (์๊ฐ๋ฝ์) ๋ผ๋ ๋์์ผ๋ก ๊ตฌ๋ถํ๋ ๊ฒ์ ๋๋ค.
Touch Up Inside ๋ ๋ทฐ๋ฅผ ํฐ์นํ๊ณ ์๋ ์ํ์์ ํด๋น ๋ทฐ์ ๋ฐ์ด๋๋ฆฌ ๋ด(Inside)์์ ์๊ฐ๋ฝ์ ๋ผ๋ฉด ๋ฐ์ํฉ๋๋ค.
์ผ๋ฐ์ ์ธ ํญ ๋์์ด๋ผ๊ณ ๋ณผ ์ ์์ต๋๋ค.
Touch Up Outside ๋ ๋ทฐ๋ฅผ ํฐ์นํ๊ณ ์๋ ์ํ์์ ๋๋๊ทธํ ์ฑ๋ก ์ด๋ํ์ฌ ๋ทฐ์ ๋ฐ์ด๋๋ฆฌ ๋ฐ๊นฅ(Outside)์์ ์๊ฐ๋ฝ์ ๋ผ๋ฉด ๋ฐ์ํฉ๋๋ค.
๋!