사용자 지정 iOS 뷰 클래스를 만들고 여러 복사본을 인스턴스화하려면 어떻게합니까 (IB에서)?


114

현재 기본적으로 모두 동일한 타이머가 여러 개있는 앱을 만들고 있습니다.

타이머와 레이아웃 / 애니메이션에 대한 모든 코드를 사용하는 사용자 지정 클래스를 만들고 싶습니다. 그래서 서로 독립적으로 작동하는 5 개의 동일한 타이머를 가질 수 있습니다.

IB (xcode 4.2)를 사용하여 레이아웃을 만들었으며 타이머에 대한 모든 코드는 현재 viewcontroller 클래스에만 있습니다.

모든 것을 사용자 정의 클래스로 캡슐화 한 다음 뷰 컨트롤러에 추가하는 방법에 대해 내 두뇌를 감싸는 데 어려움을 겪고 있습니다. 어떤 도움을 주시면 감사하겠습니다.


여기에 인터넷 검색을하는 모든 사람을 위해 ... 이제 iOS에 컨테이너 뷰가 출시 된 지 5 년이되었습니다 ! 컨테이너보기는 현재 iOS에서 모든 작업을 수행하는 방법에 대한 기본적이고 중심적인 아이디어입니다 . 사용하기 매우 간단하며 컨테이너 뷰에 대한 훌륭한 (5 년 된!) 튜토리얼이 많이 있습니다. :
Fattie

2
@JoeBlow UITableViewCell또는 에서 컨테이너보기를 사용할 수 없다는 점을 제외하고는 UICollectionViewCell. 제 경우에는 컨트롤러와 컬렉션 뷰에서 여러 번 사용할 수있는 작지만 상당히 복잡한 뷰가 필요합니다. 그리고 디자이너들은 계속해서 그것을 재 작업하기 때문에 저는 레이아웃이 정의 된 단일 장소를 원합니다. 따라서 펜촉.
Echelon

답변:


142

그럼 개념적으로 대답, 당신의 타이머는 가능성의 서브 클래스해야한다 UIView대신 NSObject.

IB에서 타이머의 인스턴스를 인스턴스화하려면 UIView뷰 컨트롤러의 뷰에 끌어다 놓고 타이머의 클래스 이름으로 클래스를 설정하면됩니다.

여기에 이미지 설명 입력

#import뷰 컨트롤러의 타이머 클래스를 기억하십시오 .

편집 : IB 설계 용 (코드 인스턴스화는 개정 내역 참조)

저는 스토리 보드에 전혀 익숙하지 않지만 .xib스토리 보드 버전을 사용하는 것과 거의 동일한 파일을 사용하여 IB에서 인터페이스를 구성 할 수 있다는 것을 알고 있습니다 . 뷰 전체를 기존 인터페이스에서 .xib파일 로 복사하여 붙여 넣을 수도 있습니다 .

이를 테스트하기 위해 .xib"MyCustomTimerView.xib"라는 새 빈을 만들었습니다 . 그런 다음 뷰를 추가하고 여기에 레이블과 두 개의 버튼을 추가했습니다. 그렇게 :

여기에 이미지 설명 입력

UIView"MyCustomTimer"라는 새로운 목표 C 클래스 하위 클래스를 만들었습니다 . 내에서 .xib나는 내 설정 파일의 소유자 로 클래스를 MyCustomTimer . 이제 다른 뷰 / 컨트롤러처럼 액션과 아웃렛을 자유롭게 연결할 수 있습니다. 결과 .h파일은 다음과 같습니다.

@interface MyCustomTimer : UIView
@property (strong, nonatomic) IBOutlet UILabel *displayLabel;
@property (strong, nonatomic) IBOutlet UIButton *startButton;
@property (strong, nonatomic) IBOutlet UIButton *stopButton;
- (IBAction)startButtonPush:(id)sender;
- (IBAction)stopButtonPush:(id)sender;
@end

점프해야 할 유일한 장애물은 .xibUIView하위 클래스 에서 이것을 얻는 것 입니다 . 를 사용하면 .xib필요한 설정 이 크게 줄어 듭니다. 그리고 스토리 보드를 사용하여 타이머를로드하기 -(id)initWithCoder:때문에 호출되는 유일한 초기화 프로그램은 우리가 알고 있습니다. 따라서 구현 파일은 다음과 같습니다.

#import "MyCustomTimer.h"
@implementation MyCustomTimer
@synthesize displayLabel;
@synthesize startButton;
@synthesize stopButton;
-(id)initWithCoder:(NSCoder *)aDecoder{
    if ((self = [super initWithCoder:aDecoder])){
        [self addSubview:
         [[[NSBundle mainBundle] loadNibNamed:@"MyCustomTimerView" 
                                        owner:self 
                                      options:nil] objectAtIndex:0]];
    }
    return self;
}
- (IBAction)startButtonPush:(id)sender {
    self.displayLabel.backgroundColor = [UIColor greenColor];
}
- (IBAction)stopButtonPush:(id)sender {
    self.displayLabel.backgroundColor = [UIColor redColor];
}
@end

명명 된 메서드 loadNibNamed:owner:options:는 소리처럼 들리는대로 정확히 수행합니다. Nib을로드하고 "File 's Owner"속성을 self로 설정합니다. 배열에서 첫 번째 객체를 추출하고 이것이 Nib의 루트 뷰입니다. 뷰를 하위 뷰로 추가하고 Voila는 화면에 표시합니다.

분명히 이것은 버튼을 눌렀을 때 레이블의 배경색을 변경하지만이 예제는 당신을 잘 이해할 것입니다.

주석에 따른 참고 사항 :

무한 재귀 문제가 발생한다면이 솔루션의 미묘한 트릭을 놓쳤을 것입니다. 당신이 생각하는 일을하는 것이 아닙니다. 스토리 보드에있는보기는 표시되지 않지만 대신 다른보기를 하위보기로로드합니다. 로드되는 뷰는 펜촉에 정의 된 뷰입니다. 펜촉의 "파일 소유자"는 보이지 않는보기입니다. 멋진 부분은이 보이지 않는 뷰가 펜촉에서 가져 오는 뷰에 대한 일종의 뷰 컨트롤러로 사용할 수있는 Objective-C 클래스라는 것입니다. 예를 들어 클래스 의 IBAction메서드 MyCustomTimer는 뷰보다 뷰 컨트롤러에서 더 많이 기대할 수있는 것입니다.

부수적으로, 일부는 이것이 깨 졌다고 주장 할 수 MVC있으며 나는 다소 동의합니다. 내 관점에서 볼 때 UITableViewCell때로는 일부 컨트롤러 여야 하는 custom과 더 밀접하게 관련 되어 있습니다.

이 답변은 매우 구체적인 솔루션을 제공하기위한 것이 었습니다. 스토리 보드에 배치 된 것과 동일한 뷰 에서 여러 번 인스턴스화 할 수있는 하나의 펜촉을 만듭니다 . 예를 들어,이 타이머 중 6 개를 iPad 화면에서 한 번에 모두 쉽게 상상할 수 있습니다. 응용 프로그램에서 여러 번 사용할 뷰 컨트롤러에 대한 뷰만 지정해야하는 경우 jyavenard에서이 질문에 대해 제공 한 솔루션 이 거의 확실히 더 나은 솔루션입니다.


이것은 실제로 별도의 질문처럼 보입니다. 하지만 빨리 대답 할 수 있다고 생각합니다. 새 알림을 만들 때마다 새 알림이지만 동일한 이름이 필요한 경우 구분하기 위해 무언가를 추가하고 싶은 userInfo경우 알림 의 속성 을 사용하십시오@property(nonatomic,copy) NSDictionary *userInfo;
NJones

28
initWithCoder에서 무한 재귀를 얻습니다. 어떤 아이디어?
사라진

6
이전 스레드를 되살려 서 죄송합니다. File's Owner클래스 이름으로 설정 되어 있는지 확인 하여 무한 재귀 문제를 해결했습니다.
themanatuf

20
무한 재귀의 또 다른 이유는 xib의 루트 뷰를 사용자 정의 클래스로 설정하는 것입니다. 그렇게하지 않으려면 기본 "UIView"로 두십시오
XY

11
이 접근 방식에 대해 내 관심을 끄는 한 가지가 있습니다. 2 개의 뷰가 지금 같은 클래스에 있다는 사실이 누구에게도 귀찮지 않습니까? UIView에서 상속 된 원본 파일 .h / .m이 첫 번째이고 [self addSubview:[[[NSBundle mainBundle] loadNibNamed:@"MyCustomTimerView" owner:self options:nil] objectAtIndex:0]]; 두 번째 UIView를 추가하여 ...이게 나에게 좀 이상해 보이는데 다른 사람은 같은 느낌이 들지 않습니까? 그렇다면 이것이 디자인에 의해 만들어진 사과입니까? 아니면 누군가가 이것을 통해 찾은 해결책입니까?
SH

137

신속한 예

Xcode 10 및 Swift 4 용으로 업데이트 됨

다음은 기본적인 절차입니다. 저는 원래이 유튜브 비디오 시리즈 를 보면서 많은 것을 배웠습니다 . 나중에이 기사를 기반으로 답변을 업데이트했습니다 .

사용자 정의보기 파일 추가

다음 두 파일이 사용자 정의보기를 구성합니다.

  • 레이아웃을 포함 할 .xib 파일
  • UIView하위 클래스 로 .swift 파일

추가에 대한 세부 정보는 다음과 같습니다.

Xib 파일

프로젝트에 .xib 파일을 추가합니다 (파일> 새로 만들기> 파일 ...> 사용자 인터페이스>보기) . 나는 나의 전화입니다 ReusableCustomView.xib.

사용자 지정보기에 포함 할 레이아웃을 만듭니다. 예를 들어 a UILabelUIButton. 나중에 설정 한 크기에 관계없이 자동으로 크기가 조정되도록 자동 레이아웃을 사용하는 것이 좋습니다. ( 시뮬레이션 된 메트릭을 조정할 수 있도록 Attributes inspector에서 xib 크기에 Freeform 을 사용 했지만 필수는 아닙니다.)

여기에 이미지 설명 입력

Swift 파일

프로젝트에 .swift 파일을 추가합니다 (File> New> File ...> Source> Swift File) . 그것은의 하위 클래스 UIView이고 나는 내라고 부르고있다 ReusableCustomView.swift.

import UIKit
class ResuableCustomView: UIView {

}

Swift 파일을 소유자로 설정

.xib 파일로 돌아가서 문서 개요에서 "파일 소유자"를 클릭합니다. Identity Inspector에서 .swift 파일 의 이름을 커스텀 클래스 이름으로 씁니다 .

여기에 이미지 설명 입력

사용자 정의보기 코드 추가

ReusableCustomView.swift파일의 내용을 다음 코드로 바꿉니다 .

import UIKit

@IBDesignable
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)
        commonInit()
    }

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

    func commonInit() {
        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
    }
}

.xib 파일 이름의 철자가 올바른지 확인하십시오.

콘센트 및 작업 연결

xib 레이아웃의 레이블과 버튼에서 신속한 사용자 정의보기 코드로 드래그하여 콘센트와 작업을 연결합니다.

사용자 지정보기 사용

이제 사용자 정의보기가 완료되었습니다. UIView메인 스토리 보드에서 원하는 곳에 추가하기 만하면됩니다. ReusableCustomViewIdentity Inspector에서 보기의 클래스 이름을로 설정합니다 .

여기에 이미지 설명 입력


31
Xib보기 클래스를 ResuableCustomView로 설정하지 않고이 Xib의 소유자로 설정하는 것이 중요합니다. 그렇지 않으면 오류가 발생합니다.
RealNmae

5
예, 좋은 알림입니다. Xib의 뷰 (파일 소유자가 아닌)를 클래스 이름으로 설정하여 발생하는 무한 재귀 문제를 해결하는 데 얼마나 많은 시간을 낭비했는지 모르겠습니다.
Suragch

2
@TomCalmon, Swift 3을 사용하여 Xcode 8에서이 프로젝트를 다시 작성했고 Auto Layout이 저에게 잘 작동했습니다.
Suragch

6
다른 사람이 IB의 뷰 렌더링에 문제가있는 경우로 변경 하면 트릭 Bundle.mainBundle(for: ...)수행 된다는 것을 알았습니다 . 분명히 Bundle.main디자인 타임에 사용할 수 없습니다.
crizzis

4
이것이 @IBDesignable에서 작동하지 않는 이유는 구현하지 않았기 때문 init(frame:)입니다. 이것을 추가하고 모든 초기화 코드를 일부 commonInit()기능 으로 가져 오면 정상적으로 작동하고 IB에 표시됩니다. 자세한 정보는 여기를 참조하십시오 : stackoverflow.com/questions/31265906/…
Dimitris

39

뷰가 아닌 뷰 컨트롤러에 대한 답변 :

스토리 보드에서 xib를로드하는 더 쉬운 방법이 있습니다. 컨트롤러가에서 상속하는 MyClassController 유형이라고 가정하십시오 UIViewController.

UIViewController스토리 보드에 using IB를 추가합니다 . 클래스 유형을 MyClassController로 변경하십시오. 스토리 보드에 자동으로 추가 된보기를 삭제합니다.

호출하려는 XIB가 MyClassController.xib인지 확인하십시오.

스토리 보드가로드되는 동안 클래스가 인스턴스화되면 xib가 자동으로로드됩니다. 그 이유 UIViewController는 클래스 이름으로 명명 된 XIB를 호출하는 기본 구현 때문 입니다.


1
나는 항상 사용자 정의 뷰를 확장하지만 스토리 보드에로드하는이 작은 트릭을 알지 못했습니다. 매우 감사!
MrTristan

38
문제는 뷰 컨트롤러가 아니라 뷰에 관한 것입니다.
Ricardo

"뷰가 아닌 뷰 컨트롤러에 대한 답변 :"이라는 명확한 제목을 추가했습니다.
nacho4d

1
iOS 9.1에서 작동하지 않는 것 같습니다. 자동 생성 된 (기본) 뷰를 제거하면 VC의 viewDidLoad에서 돌아온 후 충돌합니다. XIB의보기가 기본보기 대신 연결되었다고 생각하지 않습니다.
제프

1
내가 얻는 예외는 'Could not load NIB in bundle: 'NSBundle </Users/me/.../MyAppName.app> (loaded)' with name 'uB0-aR-WjG-view-4OA-9v-tyV''. whacko nibName에 유의하십시오. 펜촉에 올바른 클래스 이름을 사용하고 있습니다.
제프

16

이것은 실제로 답은 아니지만이 접근 방식을 공유하는 것이 도움이된다고 생각합니다.

목표 -C

  1. CustomViewWithXib.hCustomViewWithXib.m 을 프로젝트로 가져 오기
  2. 동일한 이름 (.h / .m / .xib)으로 사용자 정의보기 파일 생성
  3. CustomViewWithXib에서 사용자 정의 클래스 상속

빠른

  1. CustomViewWithXib.swift 를 프로젝트로 가져 오기
  2. 동일한 이름 (.swift 및 .xib)으로 사용자 정의보기 파일 생성
  3. CustomViewWithXib에서 사용자 정의 클래스 상속

선택 사항 :

  1. xib 파일로 이동하여 일부 요소를 연결해야하는 경우 사용자 지정 클래스 이름으로 소유자를 설정합니다 (자세한 내용은 Swift 파일을 @Suragch 응답의 소유자로 만들기 부분 참조).

이제 스토리 보드에 사용자 정의보기를 추가 할 수 있으며 표시됩니다. :)

CustomViewWithXib.h :

 #import <UIKit/UIKit.h>

/**
 *  All classes inherit from CustomViewWithXib should have the same xib file name and class name (.h and .m)
 MyCustomView.h
 MyCustomView.m
 MyCustomView.xib
 */

// This allows seeing how your custom views will appear without building and running your app after each change.
IB_DESIGNABLE
@interface CustomViewWithXib : UIView

@end

CustomViewWithXib.m :

#import "CustomViewWithXib.h"

@implementation CustomViewWithXib

#pragma mark - init methods

- (instancetype)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        // load view frame XIB
        [self commonSetup];
    }
    return self;
}

- (instancetype)initWithCoder:(NSCoder *)aDecoder {
    self = [super initWithCoder:aDecoder];
    if (self) {
        // load view frame XIB
        [self commonSetup];
    }
    return self;
}

#pragma mark - setup view

- (UIView *)loadViewFromNib {
    NSBundle *bundle = [NSBundle bundleForClass:[self class]];

    //  An exception will be thrown if the xib file with this class name not found,
    UIView *view = [[bundle loadNibNamed:NSStringFromClass([self class])  owner:self options:nil] firstObject];
    return view;
}

- (void)commonSetup {
    UIView *nibView = [self loadViewFromNib];
    nibView.frame = self.bounds;
    // the autoresizingMask will be converted to constraints, the frame will match the parent view frame
    nibView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
    // Adding nibView on the top of our view
    [self addSubview:nibView];
}

@end

CustomViewWithXib.swift :

import UIKit

@IBDesignable
class CustomViewWithXib: UIView {

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

        commonSetup()
    }

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

        commonSetup()
    }

    // MARK: setup view 

    private func loadViewFromNib() -> UIView {
        let viewBundle = NSBundle(forClass: self.dynamicType)
        //  An exception will be thrown if the xib file with this class name not found,
        let view = viewBundle.loadNibNamed(String(self.dynamicType), owner: self, options: nil)[0]
        return view as! UIView
    }

    private func commonSetup() {
        let nibView = loadViewFromNib()
        nibView.frame = bounds
        // the autoresizingMask will be converted to constraints, the frame will match the parent view frame
        nibView.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
        // Adding nibView on the top of our view
        addSubview(nibView)
    }
}

여기에서 몇 가지 예를 찾을 수 있습니다 .

도움이되기를 바랍니다.


그래서 우리는보기 uicontrols을 위해 함께 IBOutlet 추가하는 방법
아 므르 화가

1
안녕하세요 @AmrAngry, 뷰를 연결하려면 4 단계를 수행해야합니다. xib 파일로 이동하여 "파일 소유자"에서 클래스를 설정하고 "ctrl"터치를 탭하여 뷰를 인터페이스로 끌어서 놓습니다. 당신이 도움이 어떤 질문, 희망이있는 경우이 예제와 코드 소스는, 주저하지 말고
HamzaGhazouani

리플레이 해주셔서 감사합니다. 저는 이미이 작업을하고 있지만이 부분에서는 제 문제가 아닌 것 같아요. 내 문제는 내가이 UIDatePIcker를 추가하고 값이 변경 세트와 uiControleer 이벤트에의 ViewController에서이 컨트롤 목표 조치를 추가하려고하지만 방법은 / 화재 호출되지 않습니다
아 므르 화가

1
내가 피커보기를 추가하여 예를 업데이트 @AmrAngry, 그것은 당신을 도울 것입니다 수 있습니다 : github.com/HamzaGhazouani/Stackoverflow/tree/master/...
HamzaGhazouani

1
오랫동안 이것을 찾고 있었는데 감사합니다.
MH175
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.