참조
https://betterprogramming.pub/ios-view-code-a-solution-to-keep-the-encapsulation-f378dc8b1502
이전에 살펴본 뷰를 코드로 짜는 방식에서 좀 더 나아간 글임니다.
이번에는 뷰를 다루는 코드에서 좀 더 캡슐화를 진행한 버전을 설명하였다.
먼저 단일 이니셜라이저에서 모든 작업을 중앙 집중화할 새 기본 클래스를 만들고 있습니다.
public class BaseView: UIView {
public override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupView() {
}
}
위 setupView()
함수가 모든 설정을 담당하고 있습니다. 하지만 뷰코드 프로토콜에 있는 buildHierarchy, setupConstraints
들은 하위 클래스에 존재합니다. 상위 클래스에서 이를 알려면 어떻게 해야 할까요 ?
이 때 우리는 계산 속성을 활용합니다. 하위 클래스가 확장할 수 있는 계산 속성에 계층 구조와 제약 조건에 데이터를 저장해야 합니다.
struct HierarchyRelation {
let parentView: UIView
let subViews: [UIView]
init(parentView: UIView, subViews: UIView...) {
self.parentView = parentView
self.subViews = subViews
}
func makeHierarchy() {
if let stackView = parentView as? UIStackView {
for view in subViews {
stackView.addArrangedSubview(view)
}
} else {
for view in subViews {
parentView.addSubview(view)
}
}
}
}
// BaseView
open var hierarchy: [WCHierarchyRelation] { [] }
public override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupView() {
for relation in hierarchy {
relation.makeHierarchy()
}
}
상위 뷰가 스택 뷰인지 아닌지에 따라 구별해줍니다.
그런 다음 베이스뷰를 수정해줍니다.
UIView
클래스에 NSLayoutConstraint
배열인 제약조건이 계산 속성으로 존재하고 있습니다. 이를 setupView
함수 내부에서 활성화 시켜줍니다.
private func setupView() {
for relation. in hierarchy {
relation.makeHierarchy()
}
NSLayoutConstraint.activate(constraints)
}
우리는 이제 위 이니셜라이저에 의해 활성화될 각 하위 클래스에 대한 제약 조건들을 전달해야 합니다.
// ProfileView, 실제 사용사례
override public var hierarchy: [HierarchyRelation] {
super.hierarchy + [HierarchyRelation(parentView: imageView, subViews: stackView), HierarchyRelation(parentView: stackView, subViews: nameLabel, ageLabel, occupationLabel, genderLabel)]
}
override public var constraints: [NSLayoutConstraint] {
super.constraints + [
imageView.centerYAnchor.constraint(equalTo: centerYAnchor),
imageView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: Constants.imageLeading),
imageView.heightAnchor.constraint(equalToConstant: Constants.imageHeight),
...
]
}
이를 통해 코드로 뷰를 짜는 파이프라인의 캡슐화를 진행해보았습니다. 그러기 위해서 뷰의 계층 구조와 제약 조건을 데이터 모델로 전달을 해야 하긴 하지만, 파이프라인이 안전해지는 장점이 있습니다. 파이프라인에 대한 트리거가 기본 뷰 내부 이니셜라이저에 유지 되고 계산 프로퍼티의 계층 및 제약조건에 관한 데이터만 볼 수 있기 때문입니다.
이번 글의 요약은 뷰 코드 메소드를 노출하는 문제에 대해 해결한 점입니다. 동일 모듈의 다른 위치에서 호출할 수 있는 일부 메서드 내에서 선언하는 대신 일부 계산 프로퍼티 내에서 데이터를 선언하고 이니셜라이저에서 파이프라인만 트리거합니다.