Làm cách nào để tạo một lớp chế độ xem iOS tùy chỉnh và tạo nhiều bản sao của nó (trong IB)?


114

Tôi hiện đang tạo một ứng dụng sẽ có nhiều bộ hẹn giờ, về cơ bản tất cả đều giống nhau.

Tôi muốn tạo một lớp tùy chỉnh sử dụng tất cả mã cho bộ hẹn giờ cũng như bố cục / hoạt ảnh, vì vậy tôi có thể có 5 bộ hẹn giờ giống nhau hoạt động độc lập với nhau.

Tôi đã tạo bố cục bằng IB (xcode 4.2) và tất cả mã cho bộ hẹn giờ hiện chỉ nằm trong lớp bộ điều khiển chế độ xem.

Tôi đang gặp khó khăn trong việc gói gọn bộ não của mình về cách đóng gói mọi thứ vào một lớp tùy chỉnh và sau đó thêm nó vào bộ điều khiển chế độ xem, mọi trợ giúp sẽ được đánh giá cao.


Đối với bất kỳ ai truy cập vào đây ... bây giờ đã 5 năm kể từ lượt xem vùng chứa đến iOS! Chế độ xem vùng chứa là ý tưởng trung tâm, cơ bản về cách bạn làm mọi thứ trong iOS hiện tại. Họ là cực kỳ đơn giản để sử dụng, và có bất kỳ số lượng hướng dẫn tuyệt vời (năm tuổi!) Trên quan điểm chứa xung quanh, ví dụ , ví dụ
Fattie

2
@JoeBlow Ngoại trừ bạn không thể sử dụng chế độ xem vùng chứa trong a UITableViewCellhoặc UICollectionViewCell. Trong trường hợp của tôi, tôi cần một chế độ xem nhỏ nhưng khá phức tạp mà tôi có thể sử dụng nhiều lần trong bộ điều khiển và chế độ xem bộ sưu tập. Và các nhà thiết kế tiếp tục làm lại nó, vì vậy tôi muốn một nơi duy nhất bố cục được xác định. Do đó, một ngòi bút.
Echelon

Câu trả lời:


142

Để trả lời về mặt khái niệm, bộ đếm thời gian của bạn có thể phải là một lớp con của UIView thay vì NSObject.

Để khởi tạo một phiên bản bộ đếm thời gian của bạn trong IB, chỉ cần kéo ra UIView thả nó trên khung nhìn của bộ điều khiển chế độ xem của bạn và đặt lớp của nó thành tên lớp của bộ hẹn giờ của bạn.

nhập mô tả hình ảnh ở đây

Nhớ #import lớp hẹn giờ của bạn trong bộ điều khiển chế độ xem của bạn.

Chỉnh sửa: cho thiết kế IB (đối với phần tạo mã, hãy xem lịch sử sửa đổi)

Tôi không quen lắm với bảng phân cảnh, nhưng tôi biết rằng bạn có thể xây dựng giao diện của mình trong IB bằng cách sử dụng .xib tệp gần giống với việc sử dụng phiên bản bảng phân cảnh; Bạn thậm chí có thể sao chép và dán toàn bộ chế độ xem của mình từ giao diện hiện có vào .xibtệp.

Để kiểm tra điều này, tôi đã tạo một khoảng trống mới .xibcó tên "MyCustomTimerView.xib". Sau đó, tôi thêm một chế độ xem, và thêm một nhãn và hai nút. Như vậy:

nhập mô tả hình ảnh ở đây

Tôi đã tạo một lớp con lớp mục tiêu-C mới UIViewcó tên "MyCustomTimer". Trong của tôi, .xibtôi đặt lớp Chủ sở hữu tệp của tôi là MyCustomTimer . Giờ đây, tôi có thể tự do kết nối các hành động và cửa hàng giống như bất kỳ chế độ xem / bộ điều khiển nào khác. Kết quả.h trông như thế này:

@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

Rào cản duy nhất còn lại để nhảy là nhận được điều này .xibtrên UIViewlớp con của tôi . Sử dụng một .xibcắt giảm đáng kể thiết lập cần thiết. Và vì bạn đang sử dụng bảng phân cảnh để tải bộ hẹn giờ mà chúng tôi biết -(id)initWithCoder:là bộ khởi tạo duy nhất sẽ được gọi. Vì vậy, đây là tệp triển khai trông như thế nào:

#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

Phương thức có tên loadNibNamed:owner:options: thực hiện chính xác những gì nó giống như nó. Nó tải Nib và đặt thuộc tính "Chủ sở hữu tệp" thành chính nó. Chúng tôi trích xuất đối tượng đầu tiên trong mảng và đó là chế độ xem gốc của Nib. Chúng tôi thêm chế độ xem dưới dạng chế độ xem phụ và Thì đó là chế độ xem trên màn hình.

Rõ ràng là điều này chỉ thay đổi màu nền của nhãn khi các nút được nhấn, nhưng ví dụ này sẽ giúp bạn thực hiện tốt.

Ghi chú dựa trên nhận xét:

Cần lưu ý rằng nếu bạn đang gặp vấn đề đệ quy vô hạn, bạn có thể đã bỏ lỡ mẹo nhỏ của giải pháp này. Nó không làm những gì bạn nghĩ nó đang làm. Chế độ xem được đưa vào bảng phân cảnh sẽ không được nhìn thấy mà thay vào đó sẽ tải một chế độ xem khác làm chế độ xem phụ. Dạng xem mà nó tải là dạng xem được xác định trong nib. "Chủ sở hữu của tệp" trong nib là chế độ xem không nhìn thấy. Phần thú vị là chế độ xem không nhìn thấy này vẫn là một lớp Objective-C có thể được sử dụng như một bộ điều khiển chế độ xem các loại cho chế độ xem mà nó mang lại từ ngòi. Ví dụ các IBActionphương pháp trongMyCustomTimer lớp là thứ mà bạn mong đợi ở bộ điều khiển chế độ xem hơn là trong một chế độ xem.

Như một lưu ý nhỏ, một số người có thể tranh luận rằng điều này không đúng MVCvà tôi đồng ý phần nào. Theo quan điểm của tôi, nó liên quan chặt chẽ hơn đến một tùy chỉnh UITableViewCell, đôi khi cũng phải là bộ điều khiển một phần.

Cũng cần lưu ý rằng câu trả lời này đã cung cấp một giải pháp rất cụ thể; tạo một ngòi có thể được khởi tạo nhiều lần trên cùng một chế độ xem như được trình bày trên bảng phân cảnh. Ví dụ, bạn có thể dễ dàng hình dung sáu bộ hẹn giờ này cùng một lúc trên màn hình iPad. Nếu bạn chỉ cần chỉ định chế độ xem cho bộ điều khiển chế độ xem được sử dụng nhiều lần trên ứng dụng của mình thì giải pháp do jyavenard cung cấp cho câu hỏi này gần như chắc chắn là giải pháp tốt hơn cho bạn.


Đây thực sự là một câu hỏi riêng. Nhưng tôi nghĩ nó có thể được trả lời nhanh chóng, vì vậy hãy bắt đầu. Mỗi khi bạn tạo một thông báo mới thì đó là một thông báo mới nhưng nếu bạn cần chúng trùng tên và muốn thêm một cái gì đó để phân biệt chúng thì hãy sử dụng thuộc userInfotính trên thông báo@property(nonatomic,copy) NSDictionary *userInfo;
NJones 14/02/12

28
Tôi nhận được một đệ quy vô hạn từ initWithCoder. Có ý kiến ​​gì không?
biến mất

6
Xin lỗi để khôi phục một chuỗi cũ, đảm bảo rằng File's Ownerđược đặt thành tên lớp của bạn đã giải quyết được vấn đề đệ quy vô hạn của tôi.
themanatuf

20
Một lý do khác cho đệ quy vô hạn là đặt chế độ xem gốc trong xib thành lớp tùy chỉnh của bạn. Bạn không muốn làm điều đó, hãy để nó làm mặc định "UIView"
XY

11
Chỉ có một điều thu hút sự chú ý của tôi về cách tiếp cận này ... Không có ai bận tâm rằng 2 Chế độ xem đang ở cùng một lớp bây giờ? Tệp gốc .h / .m kế thừa từ UIView là tệp đầu tiên và bằng cách thực hiện [self addSubview:[[[NSBundle mainBundle] loadNibNamed:@"MyCustomTimerView" owner:self options:nil] objectAtIndex:0]]; Thêm UIView thứ hai ... Điều này có vẻ kỳ lạ đối với tôi, không ai khác cảm thấy như vậy? Nếu vậy thì đây có phải là thứ mà quả táo tạo ra theo thiết kế không? Hay giải pháp được ai đó tìm ra bằng cách làm này?
SH

137

Ví dụ về Swift

Đã cập nhật cho Xcode 10 và Swift 4

Đây là một hướng dẫn cơ bản. Ban đầu tôi đã học được rất nhiều điều này từ việc xem loạt video Youtube này . Sau đó tôi đã cập nhật câu trả lời của mình dựa trên bài viết này .

Thêm tệp chế độ xem tùy chỉnh

Hai tệp sau sẽ tạo thành dạng xem tùy chỉnh của bạn:

  • tệp .xib để chứa bố cục
  • .swift tệp dưới dạng UIView lớp con

Chi tiết để thêm chúng ở bên dưới.

Tệp Xib

Thêm tệp .xib vào dự án của bạn (Tệp> Mới> Tệp ...> Giao diện Người dùng> Chế độ xem) . Tôi đang gọi của tôiReusableCustomView.xib .

Tạo bố cục mà bạn muốn chế độ xem tùy chỉnh của mình có. Ví dụ, tôi sẽ tạo một bố cục với a UILabelvà a UIButton. Bạn nên sử dụng bố cục tự động để mọi thứ sẽ tự động thay đổi kích thước bất kể bạn đặt nó thành kích thước nào sau này. (Tôi đã sử dụng Freeform cho kích thước xib trong trình kiểm tra Thuộc tính để tôi có thể điều chỉnh các chỉ số được mô phỏng, nhưng điều này là không cần thiết.)

nhập mô tả hình ảnh ở đây

Tệp Swift

Thêm tệp .swift vào dự án của bạn (Tệp> Mới> Tệp ...> Nguồn> Tệp Swift) . Nó là một lớp con của UIViewvà tôi đang gọi là của tôi ReusableCustomView.swift.

import UIKit
class ResuableCustomView: UIView {

}

Đặt tệp Swift làm chủ sở hữu

Quay lại tệp .xib của bạn và nhấp vào "Chủ sở hữu tệp" trong Đề cương tài liệu. Trong Trình kiểm tra danh tính, hãy viết tên tệp .swift của bạn làm tên lớp tùy chỉnh.

nhập mô tả hình ảnh ở đây

Thêm mã chế độ xem tùy chỉnh

Thay thế ReusableCustomView.swiftnội dung của tệp bằng mã sau:

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

Đảm bảo viết đúng chính tả cho tên tệp .xib của bạn.

Kết nối các cửa hàng và hành động

Kết nối các cửa hàng và hành động bằng cách điều khiển kéo từ nhãn và nút trong bố cục xib sang mã chế độ xem tùy chỉnh nhanh chóng.

Sử dụng chế độ xem tùy chỉnh của bạn

Chế độ xem tùy chỉnh của bạn đã hoàn thành ngay bây giờ. Tất cả những gì bạn phải làm là thêm một UIViewbất cứ nơi nào bạn muốn trong bảng phân cảnh chính của mình. Đặt tên lớp của dạng xem thành ReusableCustomViewtrong Trình kiểm tra danh tính.

nhập mô tả hình ảnh ở đây


31
Điều quan trọng là không đặt lớp chế độ xem Xib là ResciousCustomView mà là Chủ sở hữu của Xib này. Nếu không, bạn sẽ có lỗi.
RealNmae

5
Vâng, lời nhắc tốt. Tôi không biết mình đã lãng phí bao nhiêu thời gian khi cố gắng giải quyết các vấn đề đệ quy vô hạn do đặt chế độ xem của Xib (đúng hơn là Chủ sở hữu tệp) thành tên lớp.
Suragch

2
@TomCalmon, tôi đã làm lại dự án này trong Xcode 8 với Swift 3 và Bố cục Tự động đã hoạt động tốt đối với tôi.
Suragch

6
Nếu bất kỳ ai khác gặp vấn đề với việc hiển thị chế độ xem trong IB, tôi thấy rằng việc thay đổi Bundle.mainđể Bundle(for: ...)thực hiện một mẹo nhỏ. Rõ ràng, Bundle.mainkhông có sẵn tại thời điểm thiết kế.
crizzis

4
Lý do tại sao điều này không hoạt động với @IBDesignable là bạn đã không triển khai init(frame:). Sau khi bạn thêm mã này (và kéo tất cả mã khởi tạo vào một số commonInit()chức năng), nó hoạt động tốt và hiển thị trong IB. Xem thêm thông tin tại đây: stackoverflow.com/questions/31265906/…
Dimitris

39

Câu trả lời cho bộ điều khiển chế độ xem, không phải chế độ xem :

Có một cách dễ dàng hơn để tải xib từ bảng phân cảnh. Giả sử bộ điều khiển của bạn thuộc loại MyClassController kế thừa từ đó UIViewController.

Bạn thêm một UIViewController IB bằng cách sử dụng bảng phân cảnh của mình; thay đổi loại lớp thành MyClassController. Xóa chế độ xem đã được thêm tự động trong bảng phân cảnh.

Đảm bảo rằng XIB bạn muốn gọi có tên là MyClassController.xib.

Khi lớp sẽ được khởi tạo trong quá trình tải bảng phân cảnh, xib sẽ tự động được tải. Lý do cho điều này là do việc triển khai mặc định trong UIViewControllerđó gọi XIB được đặt tên với tên lớp.


1
tôi luôn thổi phồng lượt xem tùy chỉnh, nhưng chưa bao giờ biết mẹo nhỏ này để tải nó vào bảng phân cảnh. Nhiều đánh giá cao!
MrTristan

38
Câu hỏi là về chế độ xem, không phải về bộ điều khiển chế độ xem.
Ricardo

Đã thêm tiêu đề để làm rõ, "Câu trả lời cho bộ điều khiển chế độ xem, không phải chế độ xem:"
nacho4d

1
Có vẻ như không hoạt động trong iOS 9.1. Nếu tôi xóa chế độ xem được tạo tự động (mặc định), nó sẽ bị treo sau khi quay lại từ viewDidLoad của VC. Tôi không nghĩ rằng chế độ xem trong XIB được kết nối thay cho chế độ xem mặc định.
Jeff

1
Ngoại lệ tôi nhận được là 'Could not load NIB in bundle: 'NSBundle </Users/me/.../MyAppName.app> (loaded)' with name 'uB0-aR-WjG-view-4OA-9v-tyV''. Lưu ý tên whacko nibName. Tôi đang sử dụng tên lớp chính xác cho trong nib.
Jeff

16

Đây không thực sự là một câu trả lời, nhưng tôi nghĩ sẽ hữu ích khi chia sẻ phương pháp này.

Objective-C

  1. Nhập CustomViewWithXib.hCustomViewWithXib.m vào dự án của bạn
  2. Tạo các tệp dạng xem tùy chỉnh có cùng tên (.h / .m / .xib)
  3. Kế thừa lớp tùy chỉnh của bạn từ CustomViewWithXib

Nhanh

  1. Nhập CustomViewWithXib.swift vào dự án của bạn
  2. Tạo các tệp dạng xem tùy chỉnh có cùng tên (.swift và .xib)
  3. Kế thừa lớp tùy chỉnh của bạn từ CustomViewWithXib

Không bắt buộc :

  1. Đi tới tệp xib của bạn, đặt chủ sở hữu bằng tên lớp tùy chỉnh của bạn nếu bạn cần kết nối một số phần tử (để biết thêm chi tiết, hãy xem phần Đặt tệp Swift làm chủ sở hữu của @Suragch answer)

Đó là tất cả, bây giờ bạn có thể thêm chế độ xem tùy chỉnh của mình vào bảng phân cảnh của mình và nó sẽ được hiển thị :)

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

Bạn có thể tìm thấy một số ví dụ ở đây .

Hy vọng rằng sẽ giúp.


vậy làm thế nào chúng ta có thể thêm IBOutlet cho uicontrols xem
Amr Angry

1
Xin chào @AmrAngry, để kết nối các chế độ xem bạn nên thực hiện bước 4: Đi tới tệp xib của bạn, trong "Chủ sở hữu tệp" đặt lớp của bạn và kéo và thả các chế độ xem của bạn vào giao diện bằng cách chạm vào "ctrl", tôi cập nhật mã nguồn với ví dụ này, đừng ngần ngại nếu bạn có bất kỳ câu hỏi, hy vọng giúp
HamzaGhazouani

cảm ơn rất nhiều vì sự phát lại của bạn, tôi đã làm điều này nhưng tôi đoán vấn đề của tôi không phải ở phần này. Vấn đề của tôi là tôi thêm một UIDatePIcker và cố gắng thêm một hành động mục tiêu để kiểm soát này từ ViewController để thiết lập và kiện uiControleer được giá trị thay đổi, nhưng phương pháp này là không bao giờ được gọi là / lửa
Amr Angry

1
@AmrAngry Tôi cập nhật ví dụ bằng cách thêm chế độ xem bộ chọn, có thể nó sẽ giúp bạn :) github.com/HamzaGhazouani/Stackoverflow/tree/master/…
HamzaGhazouani

1
Cảm ơn bạn Tôi đã tìm kiếm điều này trong một thời gian dài.
MH175
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.