iOS Ellen

[iOS] 반복적인 뷰 추상화하기 본문

Personal/개발일지

[iOS] 반복적인 뷰 추상화하기

Ellen61 2022. 4. 19. 16:34

 

 

 

 

 

 

 

회원가입 화면을 열심히 구현했는데 갑자기 기획자가 이메일과 비밀번호 사이에 닉네임 필드를 넣어달라고 요청했다.

하..... 진작 말해주지... 오토레이아웃이랑...언제 다 수정하지 ^^.... 라고 생각하지 말고!!

처음부터 필드가 확장성있게 구현하지 못한 개발자 잘못이다🥲

 

 

 

 

뷰의 공통적인 부분을 추상화해보자!

 

 

 

 

하나의 필드에서 타이틀 레이블, 텍스트필드가 들어간다는 공통점이 있다.

하지만 추상화할때 고민되는 부분은 각 레이블과 텍스트필드의 플레이스 홀더가 달라져야한다는 점이다.

그렇다면 이 세가지 뷰를 추상화하고 초기화되는 시점에 레이블과 텍스트필드의 플레이스 홀더를 주입받으면 되지 않을까?

 

 

 

 

 

 

// 코드 요약본

class SignUpContentsView: UIView {

    private lazy var titleLabel: UILabel = {
        ...
    }()

    private lazy var textFieldBackground: UIView = {
        ...
    }()

    private lazy var textField: UITextField = {
        ...
    }()

     convenience init(title: String, placeholder: String) {
        self.init(frame: .zero)
        titleLabel.text = title
        textField.placeholder = placeHolder
    }
}

위와 같이 타이틀과 플레이스 홀더를 초기화 시점에 주입받을 수 있게 구현했다.

 

 

 

class SignUpView: UIView {

    private var signUpContentsViews: [UIView] = [] // 공통적인 뷰를 가지고있는 배열

    private lazy var stackView: UIStackView = { // stackView 생성
        ...
    }()

    init(_ views: [UIView]) {
        self.signUpContentsViews = views
        super.init(frame: .zero)

        signUpContentsViews.forEach { // 스택뷰에 각 View들을 추가하는 작업
            stackView.addArrangedSubview($0)
        }
    }

signUpContentsViews에서 추상화한 뷰의 배열을 가지고있고

초기화하는 시점에 stackView에 각 뷰들을 추가하는 작업을 하면되는데.....

private var signUpContentsViews: [UIView] = []

init(_ views: [UIView])

모든 UIView 타입을 기대하는 방식으로 구현하면 모든 뷰가 이 배열안에 들어갈 수 있으므로 조심해야한다.

protocol을 사용하여 SignUpViewable라는 타입으로 추상화해보자!

 

 

 

 

 

 

 

protocol SignUpViewable: UIView {} // 추상화

class SignUpView: UIView {

    private var signUpContentsViews: [SignUpViewable] = [] // SignUpViewable

    private lazy var stackView: UIStackView = {
        ...
    }()

    init(_ views: [SignUpViewable]) { // SignUpViewable
        self.signUpContentsViews = views
        super.init(frame: .zero)

        signUpContentsViews.forEach {
            stackView.addArrangedSubview($0)
        }
    }

이렇게만 바꿔주면 될까? 공통적인 뷰를 구현한 SignUpContentsView를 SignUpViewable을 채택하도록 하자

 

 

 

 

 

 

class SignUpContentsView: UIView, SignUpViewable { // SignUpViewable 채택

    private lazy var titleLabel: UILabel = {
        ...
    }()

    private lazy var textFieldBackground: UIView = {
        ...
    }()

    private lazy var textField: UITextField = {
        ...
    }()

     convenience init(title: String, placeholder: String) {
        self.init(frame: .zero)
        titleLabel.text = title
        textField.placeholder = placeHolder
    }
}

자 이런식으로하면 SignUpViewable로 추상화했다. 구현 예시와같이 텍스트필드로 이루어진 뷰가 아니라

성별을 정하는 세그먼트뷰 같은 것도 SignUpViewable을 채택하면 뷰 스택에 추가될 수 있다.

즉 SignUpViewable을 채택한 UIView는 모두 추가될 수 있다!

 

 

 

 

 

 

 

class SignUpViewController: UIViewController {
    private lazy var signUpView: SignUpView = SignUpView(SignUpContentsView(title: "이메일",
                                                      placeHolder: "ex)jcrescent61@google.com")
                                              SignUpContentsView(title: "패스워드",
                                                      placeHolder: "대,소,특수문자 최소 1회 포함")
                                              SignUpContentsView(title: "패스워드 확인",
                                                      placeHolder: "동일한 비밀번호를 입력해주세요"))
    }

        override func viewDidLoad() {
        super.viewDidLoad()
        view = signUpView
    }
}

 

자 이런식으로 SignUpView(이메일뷰, 비밀번호뷰, 비밀번호확인뷰) 이런식으로 추가하면 위와 같이 편하게 뷰를 구현 가능하다!

뷰의 순서를 바꿔야하는 경우에도 배열의 순서만 바꾸면 그대로 반영된다🥳

 

 

 

 

예시코드 깃허브 바로가기

https://github.com/jcrescent61/EllenTalk

아래의 코드는 세세한것들까지 추상화했으므로 현재 글과 코드가 조금 다른 점 유의하시길 바랍니다!

직접 프로젝트를 열고 뷰를 추가해보면서 실험하면 좋은 경험이 될 것 같습니다 ㅎㅎ