Development Tip

스토리 보드에 인터페이스 설정이있는 Swift의 UIViewController에 대한 사용자 정의 초기화

yourdevel 2020. 11. 11. 20:46
반응형

스토리 보드에 인터페이스 설정이있는 Swift의 UIViewController에 대한 사용자 정의 초기화


UIViewController의 하위 클래스에 대한 사용자 지정 초기화를 작성하는 데 문제가 있습니다. 기본적으로 속성을 직접 설정하는 대신 viewController의 init 메서드를 통해 종속성을 전달하고 싶습니다. viewControllerB.property = value

그래서 내 viewController에 대한 사용자 정의 초기화를 만들고 슈퍼 지정 초기화를 호출했습니다.

init(meme: Meme?) {
        self.meme = meme
        super.init(nibName: nil, bundle: nil)
    }

뷰 컨트롤러 인터페이스는 스토리 보드에 있으며 사용자 지정 클래스의 인터페이스도 뷰 컨트롤러로 만들었습니다. 그리고 Swift는이 메소드 내에서 아무것도하지 않더라도이 init 메소드를 호출해야합니다. 그렇지 않으면 컴파일러가 불평합니다 ...

required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }

문제는 MyViewController(meme: meme)내 viewController에서 속성을 초기화하지 않는 사용자 지정 초기화를 호출하려고 할 때입니다 .

디버깅을 시도하고 있었는데, viewController에서 발견하고 init(coder aDecoder: NSCoder)먼저 호출 한 다음 나중에 사용자 지정 초기화를 호출했습니다. 그러나이 두 init 메서드는 다른 self메모리 주소를 반환 합니다.

내 viewController의 init에 문제가 있다고 의심되며 항상 구현이없는으로 반환 self됩니다 init?(coder aDecoder: NSCoder).

누구든지 viewController에 대한 사용자 정의 초기화를 올바르게 만드는 방법을 알고 있습니까? 참고 : 내 viewController의 인터페이스는 스토리 보드에 설정되어 있습니다.

다음은 내 viewController 코드입니다.

class MemeDetailVC : UIViewController {

    var meme : Meme!

    @IBOutlet weak var editedImage: UIImageView!

    // TODO: incorrect init
    init(meme: Meme?) {
        self.meme = meme
        super.init(nibName: nil, bundle: nil)
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }

    override func viewDidLoad() {
        /// setup nav title
        title = "Detail Meme"

        super.viewDidLoad()
    }

    override func viewWillAppear(animated: Bool) {
        super.viewWillAppear(animated)
        editedImage = UIImageView(image: meme.editedImage)
    }

}

위의 답변 중 하나에서 지정 했으므로 사용자 정의 초기화 방법과 스토리 보드를 모두 사용할 수 없습니다. 그러나 여전히 정적 메서드를 사용하여 스토리 보드에서 ViewController를 인스턴스화하고 추가 설정을 수행 할 수 있습니다. 다음과 같이 표시됩니다.

class MemeDetailVC : UIViewController {

    var meme : Meme!

    static func makeMemeDetailVC(meme: Meme) -> MemeDetailVC {
        let newViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("IdentifierOfYouViewController") as! MemeDetailVC

        newViewController.meme = meme

        return newViewController
    }
}

스토리 보드에서 뷰 컨트롤러 식별자로 IdentifierOfYouViewController를 지정하는 것을 잊지 마십시오. 위 코드에서 스토리 보드의 이름을 변경해야 할 수도 있습니다.


스토리 보드에서 초기화 할 때 사용자 지정 이니셜 라이저를 사용할 수 없습니다.를 사용 init?(coder aDecoder: NSCoder)하여 컨트롤러를 초기화하기 위해 스토리 보드를 디자인했습니다. 그러나 데이터를 UIViewController.

뷰 컨트롤러의 이름이 포함 detail되어 있으므로 다른 컨트롤러에서 가져 왔다고 가정합니다. 이 경우 prepareForSegue메서드를 사용 하여 세부 정보로 데이터를 보낼 수 있습니다 (Swift 3입니다).

override func prepare(for segue: UIStoryboardSegue, sender: AnyObject?) {
    if segue.identifier == "identifier" {
        if let controller = segue.destinationViewController as? MemeDetailVC {
            controller.meme = "Meme"
        }
    }
}

테스트 목적 String대신 유형의 속성을 사용했습니다 Meme. 또한 올바른 segue 식별자를 전달했는지 확인하십시오 ( "identifier"자리 표시 자일 뿐임 ).


이 작업을 수행 한 한 가지 방법은 편의 이니셜 라이저를 사용하는 것입니다.

class MemeDetailVC : UIViewController {

    convenience init(meme: Meme) {
        self.init()
        self.meme = meme
    }
}

그런 다음 MemeDetailVC를 다음과 같이 초기화합니다. let memeDetailVC = MemeDetailVC(theMeme)

이니셜 라이저에 대한 Apple의 문서 는 꽤 좋지만 개인적으로 가장 좋아하는 것은 Ray Wenderlich : Initialization in Depth 튜토리얼 시리즈입니다. 다양한 init 옵션과 작업을 수행하는 "적절한"방법에 대한 많은 설명 / 예제를 제공해야합니다.


편집 : 사용자 정의 뷰 컨트롤러에서 편의 이니셜 라이저를 사용할 수 있지만 스토리 보드 또는 스토리 보드 segue를 통해 초기화 할 때 사용자 정의 이니셜 라이저를 사용할 수 없다고 말하는 모든 사람이 맞습니다.

If your interface is set up in the storyboard and you're creating the controller completely programmatically, then a convenience initializer is probably the easiest way to do what you're trying to do since you don't have to deal with the required init with the NSCoder (which I still don't really understand).

If you're getting your view controller via the storyboard though, then you will need to follow @Caleb Kleveter's answer and cast the view controller into your desired subclass then set the property manually.


As @Caleb Kleveter has pointed out, we can't use a custom initializer while initialising from a Storyboard.

But, we can solve the problem by using factory/class method which instantiate view controller object from Storyboard and return view controller object. I think this is a pretty cool way.

Note: This is not an exact answer to question rather a workaround to solve the problem.

Make class method, in MemeDetailVC class, as follows:

// Considering your view controller resides in Main.storyboard and it's identifier is set to "MemeDetailVC"
class func `init`(meme: Meme) -> MemeDetailVC {
    let storyboard = UIStoryboard(name: "Main", bundle: nil)
    let vc = storyboard.instantiateViewController(withIdentifier: "MemeDetailVC") as? MemeDetailVC
    vc.meme = meme
    return vc
}

Usage:

let memeDetailVC = MemeDetailVC.init(meme: Meme())

There were originally a couple of answers, which were cow voted and deleted even though they were basically correct. The answer is, you can't.

When working from a storyboard definition your view controller instances are all archived. So, to init them it's required that init?(coder... be used. The coder is where all the settings / view information comes from.

So, in this case, it's not possible to also call some other init function with a custom parameter. It should either be set as a property when preparing the segue, or you could ditch segues and load the instances directly from the storyboard and configure them (basically a factory pattern using a storyboard).

In all cases you use the SDK required init function and pass additional parameters afterwards.


UIViewController class conform to NSCoding protocol which is defined as:

public protocol NSCoding {

   public func encode(with aCoder: NSCoder)

   public init?(coder aDecoder: NSCoder) // NS_DESIGNATED_INITIALIZER
}    

So UIViewController has two designated initializer init?(coder aDecoder: NSCoder) and init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?).

Storyborad calls init?(coder aDecoder: NSCoder) directly to init UIViewController and UIView,There is no room for you to pass parameters.

One cumbersome workaround is to use an temporary cache:

class TempCache{
   static let sharedInstance = TempCache()

   var meme: Meme?
}

TempCache.sharedInstance.meme = meme // call this before init your ViewController    

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder);
    self.meme = TempCache.sharedInstance.meme
}

Swift 5

You can write custom initializer for only Optional type properties.

class MyFooClass: UIViewController {
    var foo: Foo?

    init(with foo: Foo) {
        self.foo = foo
        super.init(nibName: nil, bundle: nil)
    }

    public required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        self.foo = nil
    }
}

// View controller is in Main.storyboard and it has identifier set

Class B

class func customInit(carType:String) -> BViewController 

{

let storyboard = UIStoryboard(name: "Main", bundle: nil)

let objClassB = storyboard.instantiateViewController(withIdentifier: "BViewController") as? BViewController

    print(carType)
    return objClassB!
}

Class A

let objB = customInit(carType:"Any String")

 navigationController?.pushViewController(objB,animated: true)

Swift 5 - Stored Property Initialization in UIViewController Subclass

class SomeClass: UIViewController {
    var storedProperty: String

     required init?(coder aDecoder: NSCoder) {
        self.storedProperty = "Stored Prop"
        super.init(coder: aDecoder);
     }

}

참고URL : https://stackoverflow.com/questions/35315404/custom-init-for-uiviewcontroller-in-swift-with-interface-setup-in-storyboard

반응형