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


89

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

}

이것에 대한 해결책을 얻었습니까?
user481610

답변:


49

위의 답변 중 하나에서 지정 했으므로 사용자 정의 초기화 방법과 스토리 보드를 모두 사용할 수 없습니다. 그러나 여전히 정적 메서드를 사용하여 스토리 보드에서 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를 지정하는 것을 잊지 마십시오. 위 코드에서 스토리 보드의 이름을 변경해야 할 수도 있습니다.


MemeDetailVC가 초기화 된 후 "meme"속성이 존재합니다. 그런 다음 종속성 주입이 발생하여 "meme"에 값을 할당합니다. 현재 loadView () 및 viewDidLoad는 호출되지 않았습니다. 이 두 메서드는 MemeDetailVC가 계층을보기 위해 추가 / 푸시 한 후에 호출됩니다.
Jim Yu

여기에 최고의 답변!
PascalS

아직 초기화 방법이 필요하며, 컴파일러는 밈 저장 속성이 초기화되지 않은 말할 것이다
렌드 라 쿠마

27

스토리 보드에서 초기화 할 때 사용자 지정 이니셜 라이저를 사용할 수 없습니다.를 사용 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"자리 표시 자일 뿐임 ).


1
안녕하세요,하지만 우리는 선택 사항으로 지정하는 것을 어떻게 제거 할 수 있으며 할당하지 않는 실수도하고 싶지 않습니다. 우리는 컨트롤러가 처음에 존재하기 위해 의미있는 엄격한 의존성을 원합니다.
Amber K

1
@AmberK Interface Builder를 사용하는 대신 인터페이스 프로그래밍을 살펴볼 수 있습니다.
Caleb Kleveter

좋아, 나는 또한 참조를 위해 kickstarter ios 프로젝트를 찾고 있지만 아직 뷰 모델을 보내는 방법을 말할 수 없습니다.
Amber K

23

@Caleb Kleveter가 지적했듯이 스토리 보드에서 초기화하는 동안 사용자 지정 초기화 프로그램을 사용할 수 없습니다.

하지만 Storyboard에서 뷰 컨트롤러 객체를 인스턴스화하고 뷰 컨트롤러 객체를 반환하는 팩토리 / 클래스 메서드를 사용하여 문제를 해결할 수 있습니다. 이것은 꽤 멋진 방법이라고 생각합니다.

참고 : 이것은 질문에 대한 정확한 대답이 아니라 문제를 해결하기위한 해결 방법입니다.

MemeDetailVC 클래스에서 다음과 같이 클래스 메서드를 만듭니다.

// 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
}

용법:

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

5
그러나 여전히 단점이 있으며 속성은 선택 사항이어야합니다.
Amber K

class func init (meme: Meme) -> MemeDetailVCXcode 10.2를 사용할 때 혼란스럽고 오류가 발생합니다Incorrect argument label in call (have 'meme:', expected 'coder:')
Samuël

21

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

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를 통해 초기화 할 때 사용자 정의 이니셜 라이저를 사용할 수 없다고 말하는 모든 사람이 맞습니다.

인터페이스가 스토리 보드에 설정되어 있고 컨트롤러를 완전히 프로그래밍 방식으로 생성하는 경우, 필요한 초기화를 처리 할 필요가 없기 때문에 편의 이니셜 라이저가 수행하려는 작업을 수행하는 가장 쉬운 방법 일 것입니다. NSCoder (내가 아직도 이해하지 못함).

스토리 보드를 통해 뷰 컨트롤러를 얻는 경우 @Caleb Kleveter의 답변 을 따라 뷰 컨트롤러를 원하는 하위 클래스로 캐스팅 한 다음 속성을 수동으로 설정해야합니다.


1
이해가 안되는데, 인터페이스가 스토리 보드에 설정되어 있고 프로그래밍 방식으로 생성하는 경우 스토리 보드를 사용하여 인스턴스화 메서드를 호출하여 인터페이스를로드해야합니까? convenience여기서는 어떻게 도움이 될까요?
Amber K

swift init의 클래스는 클래스의 다른 함수를 호출하여 초기화 할 수 없습니다 (구조는 가능합니다). 따라서를 호출하려면을 이니셜 라이저 로 self.init()표시해야합니다 . 그렇지 않으면 이니셜 라이저에서 a의 모든 필수 속성을 수동으로 설정 해야하며 이러한 모든 속성이 무엇인지 잘 모르겠습니다. init(meme: Meme)convenienceUIViewController
Ponyboy47

이것은 super.init ()가 아니라 self.init ()를 호출하기 때문에 UIViewController를 서브 클래 싱하지 않습니다. 여전히 기본 초기화를 사용하여 MemeDetailVC를 초기화 할 수 있으며이 MemeDetail()경우 코드가 충돌합니다
Ali

self.init ()는 구현시 super.init ()를 호출해야하거나 self.init ()가 기능적으로 동등한 경우 부모로부터 직접 상속되기 때문에 여전히 서브 클래 싱으로 간주됩니다.
Ponyboy47

6

원래 몇 가지 답변이 있었는데, 기본적으로 정확했지만 소 투표 및 삭제되었습니다. 대답은 할 수 없다는 것입니다.

스토리 보드 정의에서 작업 할 때 뷰 컨트롤러 인스턴스는 모두 보관됩니다. 따라서 초기화하려면 사용해야 init?(coder...합니다. 는 coder모든 설정 / 뷰 정보의 출처입니다.

따라서이 경우 사용자 지정 매개 변수를 사용하여 다른 초기화 함수를 호출 할 수도 없습니다. segue를 준비 할 때 속성으로 설정하거나 segue를 버리고 스토리 보드에서 직접 인스턴스를로드하고 구성 할 수 있습니다 (기본적으로 스토리 보드를 사용하는 팩토리 패턴).

모든 경우에 SDK 필수 초기화 기능을 사용하고 나중에 추가 매개 변수를 전달합니다.


4

스위프트 5

다음과 같이 사용자 정의 이니셜 라이저를 작성할 수 있습니다->

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

3

UIViewController클래스 NSCoding는 다음과 같이 정의 된 프로토콜을 따릅니다 .

public protocol NSCoding {

   public func encode(with aCoder: NSCoder)

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

그래서 UIViewController두 개의 지정된 이니셜 라이저 init?(coder aDecoder: NSCoder)init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?).

Storyborad 호출 init?(coder aDecoder: NSCoder)초기화하기 위해서 직접 UIViewController하고 UIView당신이 매개 변수를 전달 할 여지가 없습니다.

번거로운 해결 방법 중 하나는 임시 캐시를 사용하는 것입니다.

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
}


0

면책 조항 : 나는 이것을 옹호하지 않으며 그것의 복원력을 철저히 테스트하지 않았지만 놀면서 발견 한 잠재적 인 해결책입니다.

기술적으로 사용자 지정 초기화는 뷰 컨트롤러를 두 번 초기화하여 스토리 보드 구성 인터페이스를 유지하면서 수행 할 수 있습니다. 처음에는 custom을 통해 init, 두 번째 loadView()는 스토리 보드에서 뷰를 가져 오는 내부 에서 수행 할 수 있습니다.

final class CustomViewController: UIViewController {
  @IBOutlet private weak var label: UILabel!
  @IBOutlet private weak var textField: UITextField!

  private let foo: Foo!

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

  override func loadView() {
    //Only proceed if we are not the storyboard instance
    guard self.nibName == nil else { return super.loadView() }

    //Initialize from storyboard
    let storyboard = UIStoryboard(name: "Main", bundle: nil)
    let storyboardInstance = storyboard.instantiateViewController(withIdentifier: "CustomVC") as! CustomViewController

    //Remove view from storyboard instance before assigning to us
    let storyboardView = storyboardInstance.view
    storyboardInstance.view.removeFromSuperview()
    storyboardInstance.view = nil
    self.view = storyboardView

    //Receive outlet references from storyboard instance
    self.label = storyboardInstance.label
    self.textField = storyboardInstance.textField
  }

  required init?(coder: NSCoder) {
    //Must set all properties intended for custom init to nil here (or make them `var`s)
    self.foo = nil
    //Storyboard initialization requires the super implementation
    super.init(coder: coder)
  }
}

이제 앱의 다른 곳에서 사용자 지정 이니셜 라이저를 호출 CustomViewController(someParameter: foo)하고 스토리 보드에서보기 구성을받을 수 있습니다.

몇 가지 이유로 인해 이것이 훌륭한 솔루션이라고 생각하지 않습니다.

  • 초기화 전 속성을 포함하여 객체 초기화가 복제됩니다.
  • 사용자 정의에 전달 된 매개 변수는 init선택적 특성으로 저장되어야합니다.
  • 콘센트 / 속성 변경시 유지해야하는 상용구 추가

아마도 당신은 이러한 트레이드 오프를 받아 들일 수 있습니다. 있지만 위험을 감수해야 합니다.


0

이제는 instantiateInitialViewController(creator:)관계 및 쇼를 포함한 segues를 사용하여 스토리 보드의 기본 컨트롤러에 대해 사용자 정의 초기화를 수행 할 수 있습니다 .

이 기능은 Xcode 11에 추가되었으며 다음은 Xcode 11 릴리스 노트 에서 발췌 한 것입니다 .

@IBSegueAction속성으로 주석이 추가 된 뷰 컨트롤러 메서드 는 필요한 값이있는 사용자 지정 이니셜 라이저를 사용하여 코드에서 segue의 대상 뷰 컨트롤러를 만드는 데 사용할 수 있습니다. 이를 통해 스토리 보드에서 비 선택적 초기화 요구 사항이있는 뷰 컨트롤러를 사용할 수 있습니다. segue에서 @IBSegueAction소스 뷰 컨트롤러 의 메서드 로 연결을 만듭니다 . Segue 작업을 지원하는 새 OS 버전에서는 해당 메서드가 호출되고 반환되는 값은 destinationViewController에 전달 된 segue 개체의 값이됩니다 prepareForSegue:sender:. @IBSegueAction단일 소스 뷰 컨트롤러에 여러 메서드를 정의 할 수 있으므로 .NET에서 segue 식별자 문자열을 확인해야하는 필요성을 줄일 수 있습니다 prepareForSegue:sender:. (47091566)

IBSegueAction코더, 송신기 및 SEGUE의 식별자 : 방법은 세 개의 파라미터를 채택한다. 첫 번째 매개 변수는 필수이며 다른 매개 변수는 원하는 경우 메소드의 서명에서 생략 할 수 있습니다. 는 NSCoder그것이 스토리 구성된 값으로 정의있어 확인 대상 뷰 컨트롤러의 이니셜로 전달되어야한다. 이 메서드는 스토리 보드에 정의 된 대상 컨트롤러 유형과 일치하는 뷰 컨트롤러를 반환하거나 nil대상 컨트롤러가 표준 init(coder:)메서드 로 초기화되도록합니다 . 을 반환 할 필요가 없다는 것을 알고있는 경우 nil반환 유형은 선택 사항이 아닐 수 있습니다.

Swift에서 @IBSegueAction속성을 추가하십시오 .

@IBSegueAction
func makeDogController(coder: NSCoder, sender: Any?, segueIdentifier: String?) -> ViewController? {
    PetController(
        coder: coder,
        petName:  self.selectedPetName, type: .dog
    )
}

Objective-C IBSegueAction에서 반환 유형 앞에 추가 합니다.

- (IBSegueAction ViewController *)makeDogController:(NSCoder *)coder
               sender:(id)sender
      segueIdentifier:(NSString *)segueIdentifier
{
   return [PetController initWithCoder:coder
                               petName:self.selectedPetName
                                  type:@"dog"];
}

0

올바른 흐름은 지정된 이니셜 라이저를 호출하는 것입니다.이 경우에는 nibName이있는 init입니다.

init(tap: UITapGestureRecognizer)
{
    // Initialise the variables here


    // Call the designated init of ViewController
    super.init(nibName: nil, bundle: nil)

    // Call your Viewcontroller custom methods here

}

-1

// 뷰 컨트롤러는 Main.storyboard에 있으며 식별자 세트가 있습니다.

클래스 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!
}

클래스 A

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

 navigationController?.pushViewController(objB,animated: true)
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.