인기 있던 글이라도 티스토리로 옮기고 브런치는 좀 더 단순하게 운영하려고 한다.

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 ... 

'Blog History' 카테고리의 다른 글

144  (0) 2020.04.06
143  (0) 2020.04.06
141  (0) 2020.04.06
140  (0) 2020.04.06
139  (0) 2020.04.06

+ Recent posts