[ios] 스토리 보드의 외부 xib 파일에서보기로드

스토리 보드의 여러보기 컨트롤러에서보기를 사용하고 싶습니다. 따라서 외부 xib에서 뷰를 디자인하여 변경 사항이 모든 뷰 컨트롤러에 반영되도록 생각했습니다. 그러나 스토리 보드의 외부 xib에서 뷰를 어떻게로드 할 수 있습니까? 그렇지 않은 경우 상황에 맞도록 다른 대안이 있습니까?



답변

내 전체 예는 여기 있지만 아래에 요약을 제공합니다.

나열한 것

동일한 이름을 가진 .swift 및 .xib 파일을 프로젝트에 추가하십시오. .xib 파일에는 사용자 정의보기 레이아웃이 포함됩니다 (자동 레이아웃 제약 조건을 사용하는 것이 좋습니다).

빠른 파일을 xib 파일의 소유자로 만듭니다.

여기에 이미지 설명을 입력하십시오
암호

다음 코드를 .swift 파일에 추가하고 .xib 파일에서 콘센트와 작업을 연결하십시오.

import UIKit
class ResuableCustomView: UIView {

    let nibName = "ReusableCustomView"
    var contentView: UIView?

    @IBOutlet weak var label: UILabel!
    @IBAction func buttonTap(_ sender: UIButton) {
        label.text = "Hi"
    }

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

        guard let view = loadViewFromNib() else { return }
        view.frame = self.bounds
        self.addSubview(view)
        contentView = view
    }

    func loadViewFromNib() -> UIView? {
        let bundle = Bundle(for: type(of: self))
        let nib = UINib(nibName: nibName, bundle: bundle)
        return nib.instantiate(withOwner: self, options: nil).first as? UIView
    }
}

그걸 써

스토리 보드의 어느 곳에서나 사용자 정의보기를 사용하십시오. a를 추가 UIView하고 클래스 이름을 사용자 정의 클래스 이름으로 설정하십시오.

여기에 이미지 설명을 입력하십시오


답변

잠시 동안 Christopher Swasey의 접근 방식 은 내가 찾은 최고의 접근 방식 이었습니다. 나는 우리 팀의 두 명의 수석 개발자에게 그것에 대해 물었고 그들 중 하나 는 완벽한 솔루션을 가지고있었습니다 ! 그것은 크리스토퍼 스와 지 (Christopher Swasey)가 웅변 적으로 해결 한 문제를 모두 만족 시키며 보일러 플레이트 하위 클래스 코드 (필요한 접근 방식)가 필요하지 않습니다. 이 하나 잡았다 , 그러나 상당히 직관적이고 쉽게 구현할 수 있다는 이외는.

  1. .swift 파일에서 사용자 정의 UIView 클래스를 작성하여 xib를 제어하십시오. 즉MyCustomClass.swift
  2. .xib 파일을 만들고 원하는대로 스타일을 지정하십시오. 즉MyCustomClass.xib
  3. 설정 File's Owner.xib 파일은 (사용자 정의 클래스가 될 수 있습니다 MyCustomClass)
  4. 잡았다 : 휴가 class(세 이하 값 identity Inspector.xib 파일 빈에서 사용자 정의보기를 들어). 따라서 사용자 정의보기에는 지정된 클래스가 없지만 지정된 파일 소유자가 있습니다.
  5. 평소와 같이 콘센트를 연결하십시오 Assistant Editor.
    • 참고 :를 보면 Connections Inspector참조 아웃렛이 사용자 정의 클래스 (예 MyCustomClass:)를 참조하는 것이 아니라 참조라는 것을 알 수 File's Owner있습니다. 이후 File's Owner사용자 정의 클래스로 지정되고, 출구는 후크와 일 때에 프로퍼티됩니다.
  6. 클래스 선언 전에 사용자 정의 클래스에 @IBDesignable이 있는지 확인하십시오.
  7. 사용자 정의 클래스가 NibLoadable아래 참조 된 프로토콜을 준수하도록하십시오 .
    • 참고 : 사용자 정의 클래스 .swift파일 이름이 파일 이름과 다른 .xib경우 nibName속성을 파일 이름으로 설정 .xib하십시오.
  8. 아래 예제와 같이 구현 required init?(coder aDecoder: NSCoder)하고 override init(frame: CGRect)호출 setupFromNib()하십시오.
  9. 원하는 스토리 보드에 UIView를 추가하고 클래스를 사용자 정의 클래스 이름 (예 :)으로 설정하십시오 MyCustomClass.
  10. 스토리 보드에서 .xib를 놀라움과 놀라움으로 그려주는 IBDesignable을보십시오.

참조하려는 프로토콜은 다음과 같습니다.

public protocol NibLoadable {
    static var nibName: String { get }
}

public extension NibLoadable where Self: UIView {

    public static var nibName: String {
        return String(describing: Self.self) // defaults to the name of the class implementing this protocol.
    }

    public static var nib: UINib {
        let bundle = Bundle(for: Self.self)
        return UINib(nibName: Self.nibName, bundle: bundle)
    }

    func setupFromNib() {
        guard let view = Self.nib.instantiate(withOwner: self, options: nil).first as? UIView else { fatalError("Error loading \(self) from nib") }
        addSubview(view)
        view.translatesAutoresizingMaskIntoConstraints = false
        view.leadingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.leadingAnchor, constant: 0).isActive = true
        view.topAnchor.constraint(equalTo: self.safeAreaLayoutGuide.topAnchor, constant: 0).isActive = true
        view.trailingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.trailingAnchor, constant: 0).isActive = true
        view.bottomAnchor.constraint(equalTo: self.safeAreaLayoutGuide.bottomAnchor, constant: 0).isActive = true
    }
}

다음은 MyCustomClass프로토콜을 구현 하는 예제입니다 (.xib 파일 이름 MyCustomClass.xib).

@IBDesignable
class MyCustomClass: UIView, NibLoadable {

    @IBOutlet weak var myLabel: UILabel!

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

    override init(frame: CGRect) {
        super.init(frame: frame)
        setupFromNib()
    }

}

참고 : Gotcha를 놓치고 class.xib 파일 내부 의 값을 사용자 정의 클래스로 설정하면 스토리 보드에 그려지지 않으며 EXC_BAD_ACCESS앱이 무한 루프에 빠지기 때문에 앱을 실행할 때 오류가 발생합니다 init?(coder aDecoder: NSCoder)메소드를 사용하여 펜촉에서 클래스를 초기화하려고 시도한 다음 다시 Self.nib.instantiate호출 init합니다.


답변

사용하려는 xib를 작성했다고 가정하십시오.

1) UIView의 사용자 정의 서브 클래스를 작성하십시오 (파일-> 새로 작성-> 파일 …-> Cocoa Touch Class로 이동하십시오. “서브 클래스 :”가 “UIView”인지 확인하십시오).

2) xib를 기반으로하는 뷰를 초기화시이 뷰에 하위 뷰로 추가하십시오.

Obj-C에서

-(id)initWithCoder:(NSCoder *)aDecoder{
    if (self = [super initWithCoder:aDecoder]) {
        UIView *xibView = [[[NSBundle mainBundle] loadNibNamed:@"YourXIBFilename"
                                                              owner:self
                                                            options:nil] objectAtIndex:0];
        xibView.frame = self.bounds;
        xibView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
        [self addSubview: xibView];
    }
    return self;
}

스위프트 2에서

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    let xibView = NSBundle.mainBundle().loadNibNamed("YourXIBFilename", owner: self, options: nil)[0] as! UIView
    xibView.frame = self.bounds
    xibView.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
    self.addSubview(xibView)
}

스위프트 3에서

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    let xibView = Bundle.main.loadNibNamed("YourXIBFilename", owner: self, options: nil)!.first as! UIView
    xibView.frame = self.bounds
    xibView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
    self.addSubview(xibView)
}

3) 스토리 보드에서 사용하려는 곳마다 평소와 같이 UIView를 추가하고 새로 추가 된보기를 선택하고 Identity Inspector (오른쪽 상단의 세 번째 아이콘으로 선이있는 사각형 모양)로 이동하십시오. “Custom Class”아래에 “Class”로 서브 클래스의 이름을 입력하십시오.


답변

나는 항상 (1) 자동 레이아웃, (2) @IBInspectable및 (3) 콘센트로 나사를 조이는 것을 보면서 “서브 뷰로 추가”솔루션이 만족스럽지 않다는 것을 알게되었습니다 . 대신, 내가의 마법을 소개하자 awakeAfter:, NSObject방법.

awakeAfterNIB / Storyboard에서 실제로 깨어 난 객체를 완전히 다른 객체로 교체 할 수 있습니다. 그런 다음 물체는 수화 과정을 거쳐 awakeFromNib호출되고 뷰로 추가됩니다.

이것을 뷰의 “카드 보드 컷 아웃”서브 클래스에서 사용할 수 있습니다. 유일한 목적은 NIB에서 뷰를로드하고 스토리 보드에서 사용하기 위해 반환하는 것입니다. 그런 다음 포함 가능한 하위 클래스가 원본 클래스가 아닌 스토리 보드보기의 ID 관리자에서 지정됩니다. 이것이 작동하기 위해 실제로 서브 클래스 일 필요는 없지만, 서브 클래스로 만드는 것은 IB가 IBInspectable / IBOutlet 특성을 볼 수있게하는 것입니다.

이 여분의 상용구는 최적이 아닌 것처럼 보일 수 있습니다. 이상적으로 UIStoryboard는 완벽하게 처리 할 수 있기 때문 입니다. 그러나 원래 NIB 및 UIView하위 클래스를 완전히 수정하지 않은 상태 로 둘 수 있다는 이점이 있습니다. 그것이 수행하는 역할은 기본적으로 어댑터 또는 브리지 클래스의 역할이며, 유감스럽게도 추가 클래스로서 디자인 측면에서 완벽하게 유효합니다. 반면에, 클래스와의 조화를 원한다면 @BenPatch의 솔루션은 다른 사소한 변경 사항이있는 프로토콜을 구현하여 작동합니다. 어떤 솔루션이 더 나은지에 대한 질문은 프로그래머 구성의 문제로 요약됩니다. 하나는 객체 구성을 선호하는지 아니면 다중 상속을 선호하는지 여부입니다.

참고 : NIB 파일의보기에서 설정된 클래스는 동일하게 유지됩니다. 임베드 가능한 서브 클래스는 스토리 보드 에서만 사용됩니다. 코드에서 뷰를 인스턴스화하는 데 서브 클래스를 사용할 수 없으므로 추가 로직 자체가 없어야합니다. 후크 포함 해야 합니다awakeAfter .

class MyCustomEmbeddableView: MyCustomView {
  override func awakeAfter(using aDecoder: NSCoder) -> Any? {
    return (UIView.instantiateViewFromNib("MyCustomView") as MyCustomView?)! as Any
  }
}

⚠️ 여기서 중요한 단점은 스토리 보드에서 다른 뷰와 관련이없는 너비, 높이 또는 종횡비 제약 조건을 정의하면 수동으로 복사해야한다는 것입니다. 두 개의 뷰와 관련된 제약 조건은 가장 가까운 공통 조상에 설치되며 뷰는 내부에서 스토리 보드에서 깨어 났으므로 이러한 제약 조건이 슈퍼 뷰에서 수화 될 때까지 스왑이 이미 발생했습니다. 해당 뷰에만 관련된 제약 조건은 해당 뷰에 직접 설치되므로 스왑이 발생하지 않으면 복사되지 않습니다.

여기서 일어나는 것은 스토리 보드 의 뷰 설치된 제약 조건 이 새로 인스턴스화 된 뷰로 복사되며, 이미 nib 파일에 정의 된 자체 제약 조건이있을 수 있습니다. 그것들은 영향을받지 않습니다.

class MyCustomEmbeddableView: MyCustomView {
  override func awakeAfter(using aDecoder: NSCoder) -> Any? {
    let newView = (UIView.instantiateViewFromNib("MyCustomView") as MyCustomView?)!

    for constraint in constraints {
      if constraint.secondItem != nil {
        newView.addConstraint(NSLayoutConstraint(item: newView, attribute: constraint.firstAttribute, relatedBy: constraint.relation, toItem: newView, attribute: constraint.secondAttribute, multiplier: constraint.multiplier, constant: constraint.constant))
      } else {
        newView.addConstraint(NSLayoutConstraint(item: newView, attribute: constraint.firstAttribute, relatedBy: constraint.relation, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: constraint.constant))
      }
    }

    return newView as Any
  }
}  

instantiateViewFromNib형식 안전 확장 UIView입니다. 유형과 일치하는 것을 찾을 때까지 NIB의 객체를 반복합니다. 제네릭 형식은 반환 값이므로 호출 사이트에서 형식을 지정해야합니다.

extension UIView {
  public class func instantiateViewFromNib<T>(_ nibName: String, inBundle bundle: Bundle = Bundle.main) -> T? {
    if let objects = bundle.loadNibNamed(nibName, owner: nil) {
      for object in objects {
        if let object = object as? T {
          return object
        }
      }
    }

    return nil
  }
}


답변

가장 좋은 해결책은 현재 xib에 정의 된보기를 가진 사용자 정의보기 컨트롤러를 사용하고보기 컨트롤러를 추가 할 때 스토리 보드 내에서 Xcode가 생성하는 “view”속성을 삭제하는 것입니다 (이름을 설정하는 것을 잊지 마십시오) 그래도 사용자 정의 클래스).

그러면 런타임에서 자동으로 xib를 찾아서로드합니다. 모든 종류의 컨테이너보기 또는 컨텐츠보기에이 트릭을 사용할 수 있습니다.


답변

나는 별도의 스토리 보드에서alternative 사용 XIB views하는 것에 대해 생각 합니다.View Controller

그런 다음 사용자 정의보기 사용 대신에 주요 스토리 보드 container view와 함께 Embed Segue하고있는 StoryboardReference이에 사용자 지정보기 컨트롤러 의 주요 스토리 보드에서 다른보기 내부에 배치해야 볼 수 있습니다.

그런 다음 segue 준비를 통해이 내장 ViewController와 기본 뷰 컨트롤러간에 위임 및 통신을 설정할 수 있습니다 . 이 접근 방식은 UIView를 표시하는 것과는 다르지만 프로그래밍 관점에서 볼 때 훨씬 간단하고 효율적으로 동일한 목표를 달성 할 수 있습니다. 즉, 메인 스토리 보드에서 볼 수있는 재사용 가능한 사용자 정의보기가 있습니다.

추가 이점은 CustomViewController 클래스에서 로직을 구현할 수 있으며 별도의 (프로젝트에서 찾기가 더 어려운) 컨트롤러 클래스를 만들지 않고 Component를 사용하여 기본 UIViewController에 상용구 코드를 배치하지 않고 모든 위임 및 뷰 준비를 설정할 수 있다는 것입니다. 나는 이것이 재사용 가능한 구성 요소에 좋다고 생각합니다. 다른보기에 포함 할 수있는 뮤직 플레이어 구성 요소 (위젯처럼).


답변

가장 인기있는 답변은 잘 작동하지만 개념적으로 잘못되었습니다. 그들은 모두 File's owner클래스의 콘센트와 UI 구성 요소 간의 연결로 사용 됩니다. s가 File's owner아닌 최상위 개체에만 사용해야합니다 UIView. Apple 개발자 문서를 확인하십시오 . UIView를 사용 File's owner하면 이러한 바람직하지 않은 결과가 발생합니다.

  1. 당신은 당신이 사용해야하는 contentView곳에서 사용해야합니다self 합니다. 중간보기는 데이터 구조가 UI 구조를 전달하지 못하기 때문에 추악 할뿐만 아니라 구조적으로 잘못되었습니다. 선언적 UI와 반대입니다.
  2. Xib 당 하나의 UIView 만 가질 수 있습니다. Xib에는 여러 UIView가 있어야합니다.

를 사용하지 않고 우아한 방법이 있습니다 File's owner. 이 블로그 게시물을 확인하십시오 . 올바른 방법으로 설명합니다.