인기 있던 글이라도 티스토리로 옮기고 브런치는 좀 더 단순하게 운영하려고 한다.
UI 기초, SnapKit
0.01
byHAJUNHOOct 16. 2018
Android에서 Androidmanifest.xml이 설계도 이 듯, iOS에서는 info.plist 가 비슷한 역할을 한다.
info.plist의 Launch screen interface file base name은 런치 스크린을
Main storyboard file base name은 처음 실행할 스토리 보드 이름을 지정한다.
스토리 보드도 xml 파일이다.
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14313.18" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
처음 실행되는 스토리 보드는 initialViewController 체크박스에 체크를 해야 한다. 물론, GUI로 보여주지만 결국, 다음과 같이 XML이 바뀌는 것이다.
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14313.18" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="QfJ-J6-o4q">
스토리 보드에서는 ViewController를 지정할 수 있다. 이제 어떻게 코드가 흐르는지 알 수 있다.
autolayout을 위해 스토리 보드에서 constraints를 지정할 수 있지만, 왜 굳이 코드로 해야 하는지는 깊게 설명하지 않고 싶지만 굳이 말하자면, 큰 프로그램이 되는 환경 속에서 UI 작업을 지속적으로 경험해보면 코드로 할 필요성을 느낄 수밖에 없다. 애플이 편하게 MVC로 만들어주긴 했지만 말이다.(안드로이드는 무조건 xml이지...)
개발자는 항상 플랫폼 테스터도 되기 때문에 (본디 디버깅은 재미있지만) 이후 재미없는 디버깅을 하게 될 가능성이 크다. 서버와의 싱크를 맞추려니 콜백 지옥에 빠지고 그 지옥에서 나로려니 MVC를 MVVM으로 바꿔야 한다. 결론은 코드라는 이야기.
초유의 관심사 AutoLayout, 1편에서 snapkit을 쓰자고 했다.
snapkit은 pod으로 설치하고
pod 'SnapKit', '~> 4.0.0'
import SnapKit 만 하면 UIView와 그 하위 친구들에게 바로 쓸 수 있다.
var firstGroup : UIView 던
var btnVarious : UIButton이던
var iqr : UILabel이던
쉽게 constraints를 지정할 수 있다.
firstGroup.snp.makeConstraints { (make) in
make.width.equalToSuperview()
make.height.equalTo(29)
make.left.equalTo(0)
}
btnVarious.snp.makeConstraints { (make) in
make.width.equalTo(29)
make.height.equalTo(29)
make.right.equalTo(btnQuestionMark.snp.left).offset(-1)
}
iqr.snp.makeConstraints { (make) in
make.width.equalTo(110)
make.height.equalTo(28)
make.left.equalToSuperview().offset(22)
}
storyboard에서 만드는 것처럼 원하는 view에 add를 먼저 하고 제약사항을 만든다. 붙인 view가 바로 superview다. offset은 + 값과 -값이 있고 둘은 서로 반대 방향이다. 좌표계를 바꿀 수 있지만 보통은 좌측 상단이 0,0이니 어디가 + 방향 인지는 쉽게 알 수 있다.
labelAmount.snp.makeConstraints { (make) in
make.width.equalTo(52)
make.height.equalTo(17)
make.left.equalTo(amount.snp.left)
make.top.equalTo(amount.snp.bottom)
}
값을 직접 지정할 수동 있고, 다른 view의 snp를 얻어와서 맞춰줄 수도 있다.
그럼 어떤 값 지정이 가능할까? public class ConstraintMaker를 보면
public var left: ConstraintMakerExtendable {
public var top: ConstraintMakerExtendable {
public var bottom: ConstraintMakerExtendable {
public var right: ConstraintMakerExtendable {
public var leading: ConstraintMakerExtendable {
public var trailing: ConstraintMakerExtendable {
public var width: ConstraintMakerExtendable {
public var height: ConstraintMakerExtendable {
public var centerX: ConstraintMakerExtendable {
public var centerY: ConstraintMakerExtendable {
public var baseline: ConstraintMakerExtendable {
public var lastBaseline: ConstraintMakerExtendable {
public var firstBaseline: ConstraintMakerExtendable {
public var leftMargin: ConstraintMakerExtendable {
public var rightMargin: ConstraintMakerExtendable {
public var topMargin: ConstraintMakerExtendable {
public var bottomMargin: ConstraintMakerExtendable {
public var leadingMargin: ConstraintMakerExtendable {
public var trailingMargin: ConstraintMakerExtendable {
public var centerXWithinMargins: ConstraintMakerExtendable {
public var centerYWithinMargins: ConstraintMakerExtendable {
public var edges: ConstraintMakerExtendable {
public var size: ConstraintMakerExtendable {
public var center: ConstraintMakerExtendable {
public var margins: ConstraintMakerExtendable {
public var centerWithinMargins: ConstraintMakerExtendable {
이런 값들이 있고 스토리 보드를 쓰다 보면 거의 대부분 알 수 있는 것들이다. 주의할 점은 제약 사항 충돌이다. left와 right 제약 사항을 동시 지정해보면 쉽게 알 수 있을 것이다.
SnapKit의 원리는 간단하다. 다음과 같이 Int Values를 array에 넣고 나중에 for 문을 돌면서 UIKit의 API를 이용(1편 참조)하여 제약 사항을 지정하는 것이다.
internal static var none: ConstraintAttributes { return 0 }
internal static var left: ConstraintAttributes { return 1 }
internal static var top: ConstraintAttributes { return 2 }
internal static var right: ConstraintAttributes { return 4 }
internal static var bottom: ConstraintAttributes { return 8 }
10진수로 더해도 되지만, 본디 2진수의 각 자릿수가 더 편하다.
1, 2, 4, 8 이 각각 2진수에서 1, 10, 100, 1000이다. 그래서 2진수로 1111인 15의 값은
internal static var edges: ConstraintAttributes { return 15 }
에 대입되어 있다.
size는 width, height다.
internal static var size: ConstraintAttributes { return 192 }
internal static var width: ConstraintAttributes { return 64 }
internal static var height: ConstraintAttributes { return 128 }
최고수는
internal static var centerWithinMargins: ConstraintAttributes { return 786432 }
라서 UInt(32비트에서만 2,147,483,647 64비트에서는 18,446,744,073,709,551,615... 믓튼 충분)에 담겨있다. 모든 수를 보려면(internal struct ConstraintAttributes : OptionSet... 참조)
이런 속성들을
internal static func makeConstraints...
internal static func remakeConstraints...
internal static func updateConstraints...
internal static func removeConstraints(item: LayoutConstraintItem) {
let constraints = item.constraints
for constraint in constraints {
constraint.deactivateIfNeeded()
}
}
를 이용하여 적용한다.
internal func activateIfNeeded(updatingExisting: Bool = false) {
guard let item = self.from.layoutConstraintItem else {
print("WARNING: SnapKit failed to get from item from constraint. Activate will be a no-op.")
return
}
let layoutConstraints = self.layoutConstraints
if updatingExisting {
var existingLayoutConstraints: [LayoutConstraint] = []
for constraint in item.constraints {
existingLayoutConstraints += constraint.layoutConstraints
}
for layoutConstraint in layoutConstraints {
let existingLayoutConstraint = existingLayoutConstraints.first { $0 == layoutConstraint }
guard let updateLayoutConstraint = existingLayoutConstraint else {
fatalError("Updated constraint could not find existing matching constraint to update: \(layoutConstraint)")
}
let updateLayoutAttribute = (updateLayoutConstraint.secondAttribute == .notAnAttribute) ? updateLayoutConstraint.firstAttribute : updateLayoutConstraint.secondAttribute
updateLayoutConstraint.constant = self.constant.constraintConstantTargetValueFor(layoutAttribute: updateLayoutAttribute)
}
} else {
NSLayoutConstraint.activate(layoutConstraints)
item.add(constraints: [self])
}
}
UI기초 전편의 링크를 보면 constratins를 적용하는 것이 번거로웠다.
contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-[image(10)]", options: [], metrics: nil, views: viewsDict))
contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:[labTime]-|", options: [], metrics: nil, views: viewsDict))
contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-[username]-[message]-|", options: [], metrics: nil, views: viewsDict))
contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-[username]-[image(10)]-|", options: [], metrics: nil, views: viewsDict))
contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-[message]-[labTime]-|", options: [], metrics: nil, views: viewsDict))
혹은,
NSLayoutConstraint.deactivate([self])
let newConstraint = NSLayoutConstraint(
item: firstItem,
attribute: firstAttribute,
relatedBy: relation,
toItem: secondItem,
attribute: secondAttribute,
multiplier: multiplier,
constant: constant)
.
.
.
이런 식의...
그래서 쉬운 문법으로 AutoLayout을 위한 constraints를 쓸 수 있다는 점이 참 매력적인 SnapKit이다. , .snp.makeConstraints { (make) in ...
최근댓글