인프런 커뮤니티 질문&답변

문희찬님의 프로필 이미지

작성한 질문수

앨런 iOS 앱 개발 (15개의 앱을 만들면서 근본원리부터 배우는 UIKit) - MVVM까지

화면 이동과 데이터 전달 (활용 앱31강)

스토리보드로 짠 ui의 클래스의 생성자를 만들 수 없나요?

23.01.28 21:34 작성

·

505

1

class SecondViewController: UIViewController {
    @IBOutlet weak var mainLabel: UILabel!
    var someString: String?
    
    override func viewDidLoad() {
        mainLabel.text = someString
        super.viewDidLoad()
    }
    
    init(someString: String? = nil) {
        self.someString = someString
        super.init(nibName: nil, bundle: nil)
    }
    required init?(coder: NSCoder) {
        fatalError("init?(coder) error")
    }

    @IBAction func backButtonTapped(_ sender: UIButton) {
        self.dismiss(animated: true)
    }
}

스토리보드로 만든 클래스의 생성자를 만들고

storyboard?.instantiateViewController(widthIdentifier: "secondVC")를 호출하면

init?(coder: NSCoder)가 호출되어 에러가 발생하네요.

위 코드처럼 생성자를 만드는 방법은 없을까요?

답변 2

0

앨런(Allen)님의 프로필 이미지
앨런(Allen)
지식공유자

2023. 02. 11. 17:22

추가적인 내용이 생각나서, 추가 댓글을 답니다.

스토리보드로 만들었을때 스토리보드 생성자를 만드는 방법인데, 전달하고자하는 데이터(저장속성)를 옵셔널로 선언을 안하고도 가능한 방법이 있긴합니다.
(많이 활용하는 방법은 아니라.. 그냥 참고적으로 이런 것도 있구나 정도 생각하시면 좋을 것 같아요.)

class SecondViewController: UIViewController {
    
    @IBOutlet weak var mainLabel: UILabel!
    
    var someString: String
    
    override func viewDidLoad() {
        super.viewDidLoad()
        mainLabel.text = someString
    }
    
    init(coder: NSCoder, dataString: String) {
        self.someString = dataString
        super.init(coder: coder)!
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    @IBAction func backButtonTapped(_ sender: UIButton) {
        self.dismiss(animated: true, completion: nil)
    }
}


생성자를 만드실때, 스토리보드로 만들면, NSCoder가 들어간 생성자가 호출이 됩니다. 그래서 반드시

init(coder: NSCoder, dataString: String)

제가 생성자 만든 것처럼, 전달하고자 하는 데이터를 포함해서 생성자를 만드시면 되고
다면, 호출하는쪽.. 전화면에서 이 생성자를 호출하는 부분도 다시 구현하셔야 합니다.

@IBAction func storyboardWithCodeButtonTapped(_ sender: UIButton) {
        
        let hello = "다음화면으로 전달할 데이터 문자열"
        
        guard let secondVC = storyboard?.instantiateViewController(identifier: "secondVC", creator: { coder in
            SecondViewController(coder: coder, dataString: hello)
        }) else {
            fatalError("Failed to initialize view controller")
        }

        self.present(secondVC, animated: true, completion: nil)
    }

위처럼

instantiateViewController(identifier:creator:)

라는 메서드를 사용해서 해결할 수 있는 방법을 제공하고 있네요.
공식문서(https://developer.apple.com/documentation/uikit/uistoryboard/3213989-instantiateviewcontroller) 참고.

이렇게 하시면.. 전달하고자 하는 데이터를 옵셔널로 선언하지 않아도 생성자를 통해 스토리보드로 만든 뷰컨트롤러 인스턴스를 생성하면서 데이터 전달이 가능합니다.

0

앨런(Allen)님의 프로필 이미지
앨런(Allen)
지식공유자

2023. 01. 29. 03:15

스토리보드로 UI를 만드신 저장속성의 값을 할당하기 위해서 다음 생성자를 구현하시려고 하시면, 뷰컨트롤러의 필수 생성자 구현에서 fatalError를 호출하시면 당연히 안되죠. 스토리보드에서 내부적으로 required init?(coder: NSCoder)생성자를 호출하도록 되어 있기 때문에 그 내부에서 fatalError를 호출하시면... 앱이 꺼집니다.

(뷰컨트롤러의 필수생성자가 어떤 의미가 있는지는.. 글이 길어질 것 같아 아래의 링크 글을 참고 부탁드립니다.)
(참고: https://babbab2.tistory.com/171 )


물론 원하시면 아래처럼 만드실 수는 있습니다.

class SecondViewController: UIViewController {
    
    @IBOutlet weak var mainLabel: UILabel!
    
    var someString: String?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        mainLabel.text = someString   // ⭐️ super.viewDidLoad 후에 호출해야함
    }
    
    init(someString: String? = nil) {
        self.someString = someString   // ⭐️ SecondViewController의 저장속성을 셋팅하기에 super.init전에
        super.init(nibName: nil, bundle: nil)
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)    // ⭐️ 스토리보드 구현시 super.init 필요
    }

    @IBAction func backButtonTapped(_ sender: UIButton) {
        self.dismiss(animated: true, completion: nil)
    }
}

위에서 제가 별표 넣어놓은 곳 호출/할당 순서 잘 주의 해주셔야 합니다. 물론 위처럼 만들수는 있으나..

다만, 위처럼 생성자를 만들면.. 실질적인 효과가 아무것도 없습니다. 왜냐하면,

SecondViewController(someString: "안녕하세요")

이런식으로 생성자를 직접호출해서 "안녕하세요"라는 문자열 데이터를 전달해야 하는데..

그것이 아니라.. 스토리보드로 만들면, 반드시 아래처럼 호출을 하거나 해야하기 때문이죠.

storyboard?.instantiateViewController(widthIdentifier: "secondVC")


그래서 스토리보드로 만들면 저장속성을 일반적으로 옵셔널로 선언하는 것입니다. 저장속성(여기서는 someString 변수)을 옵셔널로 선언한다는 것 자체가.. 굳이 생성자를 만들 필요가 없다는 것입니다. 클래스에서 저장속성의 타입을 옵셔널 타입으로 선언해주면, nil로 초기화가 되기 때문에 생성자를 굳이 재정의 하지 않아도 괜찮습니다. (이건 생성자 관련 Swift문법 내용을 찾아보시는 면 도움이 되실 것 같고요.)
(물론 스토리보드로 만들어도 저장속성을 셋팅할 수 있는 여러가지 방법들이 있긴 합니다만.. 자주 사용하지는 않습니다.)

일반적으로 코드로 UI를 작성할때는.. 1) 저장속성 타입을 옵셔널로 선언하지 않고, 2) 생성자를 만들어서

SecondViewController(someString: "안녕하세요")

이렇게 초기화를 해줄 수도 있으나.. 그럼에도 생성자를 만드는 것 자체가 귀찮고 복잡한 코드가 되기 때문에 저장 속성을 옵셔널로 선언해주는 것이 더 편한 코드라고 생각하기는 합니다.

 

감사합니다. :)

문희찬님의 프로필 이미지
문희찬
질문자

2023. 01. 29. 09:46

주말 아침에 감사드립니다. 생성자가 있으면 코드를 관리하기가 쉽다고 생각해서 한번 만들어봤어요. 친절하게 설명해주셔서 더 많이 배워가네요. 감사합니다!!

앨런(Allen)님의 프로필 이미지
앨런(Allen)
지식공유자

2023. 01. 29. 20:44

열심히 하고 계시군요!!ㅎㅎ 화이팅 :)