본문 바로가기

SwiftUI View Custom Initializer

기술적인 이야기/애플 플랫폼 개발 2021. 5. 17.
반응형

SwiftUI View 구조체는 자동으로 @Binding 프로퍼티를 초기화 매개변수로 지정하는 생성자를 만든다. 바인딩은 당연하게도 외부에서 값을 전달받아야 하는 프로퍼티이기 때문이다.

그런데 이렇게 하나라도 바인딩 프로퍼티가 있으면 나만의 생성자를 작성하는 것에서 난관이 있을 수 있다.

728x90

예를 보자. 아래는 CustomView 라는 View에 바인딩 프로퍼티가 하나 선언된 예다.

struct CustomView: View {
    @Binding var name: String
    ...
}

앞서 이야기했다시피 바인딩 프로퍼티는 자동으로 생성자에 매개변수로 값을 전달받는 인터페이스를 가진다. 그래서 위의 뷰는 아래와 같은 형태로 인스턴스를 생성해서 사용해야 한다.

struct ContentView: View {
    @State private var name = "Noname"

    var body: some View {
        ...
        CustomView(name: name)
        ...
    }
}

name을 전달하는 모습이 보인다.

여기까진 별 문제가 없다. 아주 편하게 반응형 뷰 프로그래밍을 할 수 있다.

문제

이제 본격적으로 원하는 것을 해보자.

인스턴스 생성 도중 추가로 해야 할 일이 있다면 아래와 같이 생성자를 구현할 수 있을 것이다. 아래 예에서 단순하게 생성자에서 뭔가를 콘솔에 찍는 코드를 작성해봤다.

struct CustomView: View {
    @Binding var name: String
    ...
    init() {
        print("Initializing CustomView...")
    }
}

하지만 이 init 생성자의 인터페이스는 원하는 모습이 아니다. 왜냐하면 name 바인딩 프로퍼티의 값을 매개변수로 받아야 정상 동작을 하는 코드인데 그게 없다. 즉 이 생성자는 호출될 일이 없다는 말이다.

그렇다면 생성자에서 매개변수로 바인딩 프로퍼티를 받는 인터페이스로 바꾸면 될 것 같다. 그래서 init을 아래와 같이 수정했다.

    init(name: String) {
        print("Initializing CustomView...")
    }

그런데 여기서 약간 난관이 발생한다. 위와 같이 선언하면 컴파일 오류가 발생하기 때문이다. 아래 스크린샷처럼 여러 오류 문구를 볼 수 있다.

아앗 아아

해결하기

위의 CustomViewname 프로퍼티는 @Binding으로 선언되어 있다. 이 프로퍼티는 일반 프로퍼티와는 다르게 Binding이라는 Wrapper로 감싸여있다.

그래서 init 생성자에서 Binding을 이용해 아래와 같이 코드를 작성할 수 있다.

struct CustomView: View {
    @Binding var name: String
    ...
    init(name: Binding<String>) {
        self.name = name
        print("Initializing CustomView...")
    }
}

이제 문제는 해결되었다 라고 생각했는데 아니다. 매개변수 인터페이스에서는 이제 오류가 발생하지 않는다. 다만 여기서 self.name = name 코드 부분이 문제가 된다. 아래와 같은 컴파일 오류가 발생한다.

Cannot assign value of type 'String' to type 'Binding<String>'

바인딩은 좀 독특하게 구현이 되어있나 보다. 하긴 Property Wrapper는 뭔가를 감싸서 추가로 해야 할 일을 생략하게 만드는 것이 목표일 테고 바인딩도 그럴 것이다.

다행히도 이 코드의 문제는 쉽게 바로잡을 수 있다. 초기화 하려는 바인딩 프로퍼티의 이름 앞에 밑줄(underscore)를 붙여주면 된다.

struct CustomView: View {
    @Binding var name: String
    ...
    init(name: Binding<String>) {
        self._name = name
        print("Initializing CustomView...")
    }
}

self._name으로 프로퍼티 앞에 밑줄을 붙여놓은 점에 주의해서 보자. 즉 @Binding으로 선언된 프로퍼티는 내부적으로 동일한 이름 앞에 밑줄을 붙인 프로퍼티를 숨겨서 자동으로 선언한다는 것으로 추측해 볼 수 있다. 그래서 이런 식으로 바인딩 프로퍼티의 값을 초기화하려면 그 숨겨진 프로퍼티 자체를 초기화해야 한다는 말이 된다.

결과적으로 이 코드로 문제도 해결하였고 원하던 바를 이루었다.

이렇게 쓸 일이 과연 자주 있을지는 모르겠지만 의외로 모르니 삽질이 컸던 것 같다. 이렇게 또 삽질로 지식을 습득해간다.

관련된 링크들

 

SwiftUI Property Wrapper 팁 모음

여러가지 Property Wrapper와 관련된 팁을 정리하는 글

seorenn.github.io

728x90
반응형

댓글