[TIL]코드로 뷰를 짜는 방법에 관해 - 2

rbw·2022년 12월 27일
0

TIL

목록 보기
63/97

iOS View Code: A Solution To Keep the Encapsulation of the Pipeline

참조

https://betterprogramming.pub/ios-view-code-a-solution-to-keep-the-encapsulation-f378dc8b1502

이전에 살펴본 뷰를 코드로 짜는 방식에서 좀 더 나아간 글임니다.


이번에는 뷰를 다루는 코드에서 좀 더 캡슐화를 진행한 버전을 설명하였다.

Create a Base Class for Your Views

먼저 단일 이니셜라이저에서 모든 작업을 중앙 집중화할 새 기본 클래스를 만들고 있습니다.

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들은 하위 클래스에 존재합니다. 상위 클래스에서 이를 알려면 어떻게 해야 할까요 ?

이 때 우리는 계산 속성을 활용합니다. 하위 클래스가 확장할 수 있는 계산 속성에 계층 구조와 제약 조건에 데이터를 저장해야 합니다.

Create a Data Model for Describing the Hierarchy

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()
    }
}

상위 뷰가 스택 뷰인지 아닌지에 따라 구별해줍니다.

그런 다음 베이스뷰를 수정해줍니다.

Constraints

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),
        ...
    ]
}

이를 통해 코드로 뷰를 짜는 파이프라인의 캡슐화를 진행해보았습니다. 그러기 위해서 뷰의 계층 구조와 제약 조건을 데이터 모델로 전달을 해야 하긴 하지만, 파이프라인이 안전해지는 장점이 있습니다. 파이프라인에 대한 트리거가 기본 뷰 내부 이니셜라이저에 유지 되고 계산 프로퍼티의 계층 및 제약조건에 관한 데이터만 볼 수 있기 때문입니다.

이번 글의 요약은 뷰 코드 메소드를 노출하는 문제에 대해 해결한 점입니다. 동일 모듈의 다른 위치에서 호출할 수 있는 일부 메서드 내에서 선언하는 대신 일부 계산 프로퍼티 내에서 데이터를 선언하고 이니셜라이저에서 파이프라인만 트리거합니다.

profile
hi there 👋

0개의 댓글