Làm cách nào để tránh UITableViewControll lớn và vụng về trên iOS?


36

Tôi gặp sự cố khi triển khai mô hình MVC trên iOS. Tôi đã tìm kiếm trên Internet nhưng dường như không tìm thấy bất kỳ giải pháp tốt đẹp nào cho vấn đề này.

Nhiều UITableViewControllertriển khai dường như là khá lớn. Hầu hết các ví dụ tôi đã thấy cho phép UITableViewControllerthực hiện <UITableViewDelegate><UITableViewDataSource>. Những triển khai này là một lý do lớn tại sao UITableViewControllerđang trở nên lớn. Một giải pháp sẽ là tạo các lớp riêng biệt thực hiện <UITableViewDelegate><UITableViewDataSource>. Tất nhiên các lớp này sẽ phải có một tài liệu tham khảo đến UITableViewController. Có bất kỳ nhược điểm sử dụng giải pháp này? Nói chung, tôi nghĩ rằng bạn nên ủy thác chức năng cho các lớp "Người trợ giúp" khác hoặc tương tự, bằng cách sử dụng mẫu ủy nhiệm. Có bất kỳ cách thiết lập tốt để giải quyết vấn đề này?

Tôi không muốn mô hình chứa quá nhiều chức năng, cũng như chế độ xem. Tôi tin rằng logic nên thực sự nằm trong lớp trình điều khiển, vì đây là một trong những nền tảng của mẫu MVC. Nhưng câu hỏi lớn là:

Làm thế nào bạn nên chia bộ điều khiển của việc triển khai MVC thành các phần nhỏ hơn có thể quản lý được? (Áp dụng cho MVC trong iOS trong trường hợp này)

Có thể có một mô hình chung để giải quyết điều này, mặc dù tôi đặc biệt tìm kiếm một giải pháp cho iOS. Xin cho một ví dụ về một mô hình tốt để giải quyết vấn đề này. Vui lòng cung cấp một đối số tại sao giải pháp của bạn là tuyệt vời.


1
"Cũng là một lý lẽ tại sao giải pháp này là tuyệt vời." :)
thỉnh thoảng

1
Đó là một chút bên cạnh quan điểm, nhưng UITableViewControllercơ học có vẻ khá lạ đối với tôi, vì vậy tôi có thể liên quan đến vấn đề. Tôi thực sự vui vì tôi sử dụng MonoTouch, bởi vì MonoTouch.Dialogđặc biệt làm cho nó dễ dàng hơn để làm việc với các bảng trên iOS. Trong khi đó, tôi tò mò không biết những người khác, những người hiểu biết hơn có thể đề nghị gì ở đây ...
Patryk wiek

Câu trả lời:


43

Tôi tránh sử dụng UITableViewController, vì nó đặt nhiều trách nhiệm vào một đối tượng. Do đó tôi tách UIViewControllerlớp con khỏi nguồn dữ liệu và ủy nhiệm. Trách nhiệm của bộ điều khiển khung nhìn là chuẩn bị chế độ xem bảng, tạo nguồn dữ liệu với dữ liệu và nối các thứ đó lại với nhau. Thay đổi cách trình bày bảng xem có thể được thực hiện mà không thay đổi trình điều khiển chế độ xem và thực sự có thể sử dụng cùng một trình điều khiển chế độ xem cho nhiều nguồn dữ liệu theo mô hình này. Tương tự, thay đổi dòng công việc của ứng dụng có nghĩa là thay đổi bộ điều khiển xem mà không cần lo lắng về những gì xảy ra với bảng.

Tôi đã thử tách các giao thức UITableViewDataSourcevà các UITableViewDelegategiao thức thành các đối tượng khác nhau, nhưng cuối cùng thường bị phân tách sai vì hầu hết mọi phương thức trên đại biểu cần đào sâu vào nguồn dữ liệu (ví dụ: khi chọn, đại biểu cần biết đối tượng nào được đại diện bởi hàng được chọn). Vì vậy, tôi kết thúc với một đối tượng duy nhất là cả nguồn dữ liệu và đại biểu. Đối tượng này luôn cung cấp một phương thức -(id)tableView: (UITableView *)tableView representedObjectAtIndexPath: (NSIndexPath *)indexPathmà cả hai khía cạnh nguồn dữ liệu và đại biểu cần biết những gì họ đang làm việc.

Đó là mối quan tâm "cấp 0" của tôi. Cấp 1 được tham gia nếu tôi phải đại diện cho các loại đối tượng khác nhau trong cùng một chế độ xem bảng. Ví dụ, hãy tưởng tượng rằng bạn phải viết ứng dụng Danh bạ cho một số liên lạc, bạn có thể có các hàng đại diện cho số điện thoại, các hàng khác đại diện cho địa chỉ, các hàng khác đại diện cho địa chỉ email, v.v. Tôi muốn tránh cách tiếp cận này:

- (UITableViewCell *)tableView: (UITableView *)tableView cellForRowAtIndexPath: (NSIndexPath *)indexPath {
  id object = [self tableView: tableView representedObjectAtIndexPath: indexPath];
  if ([object isKindOfClass: [PhoneNumber class]]) {
    //configure phone number cell
  }
  else if …
}

Hai giải pháp đã trình bày cho đến nay. Một là tự động xây dựng bộ chọn:

- (UITableViewCell *)tableView: (UITableView *)tableView cellForRowAtIndexPath: (NSIndexPath *)indexPath {
  id object = [self tableView: tableView representedObjectAtIndexPath: indexPath];
  NSString *cellSelectorName = [NSString stringWithFormat: @"tableView:cellFor%@AtIndexPath:", [object class]];
  SEL cellSelector = NSSelectorFromString(cellSelectorName);
  return [self performSelector: cellSelector withObject: tableView withObject: object];
}

- (UITableViewCell *)tableView: (UITableView *)tableView cellForPhoneNumberAtIndexPath: (NSIndexPath *)indexPath {
  // configure phone number cell
}

Trong phương pháp này, bạn không cần chỉnh sửa if()cây sử thi để hỗ trợ một loại mới - chỉ cần thêm phương thức hỗ trợ lớp mới. Đây là một cách tiếp cận tuyệt vời nếu chế độ xem bảng này là cách duy nhất cần thể hiện các đối tượng này hoặc cần trình bày chúng theo một cách đặc biệt. Nếu cùng một đối tượng sẽ được biểu diễn trong các bảng khác nhau với các nguồn dữ liệu khác nhau, cách tiếp cận này bị phá vỡ vì các phương thức tạo ô cần chia sẻ trên các nguồn dữ liệu mà bạn có thể định nghĩa một siêu lớp phổ biến cung cấp các phương thức này hoặc bạn có thể làm điều này:

@interface PhoneNumber (TableViewRepresentation)

- (UITableViewCell *)tableView: (UITableView *)tableView representationAsCellForRowAtIndexPath: (NSIndexPath *)indexPath;

@end

@interface Address (TableViewRepresentation)

//more of the same…

@end

Sau đó, trong lớp nguồn dữ liệu của bạn:

- (UITableViewCell *)tableView: (UITableView *)tableView cellForRowAtIndexPath: (NSIndexPath *)indexPath {
  id object = [self tableView: tableView representedObjectAtIndexPath: indexPath];
  return [object tableView: tableView representationAsCellForRowAtIndexPath: indexPath];
}

Điều này có nghĩa là bất kỳ nguồn dữ liệu nào cần hiển thị số điện thoại, địa chỉ, v.v. chỉ có thể hỏi bất kỳ đối tượng nào được đại diện cho một ô xem bảng. Bản thân nguồn dữ liệu không còn cần phải biết bất cứ điều gì về đối tượng được hiển thị.

"Nhưng chờ đã," tôi nghe thấy một người đối thoại xen kẽ giả thuyết, "không phải nó phá vỡ MVC sao? Không phải bạn đã đưa chi tiết xem vào một lớp mô hình sao?"

Không, nó không phá vỡ MVC. Bạn có thể nghĩ về các danh mục trong trường hợp này như là một triển khai của Decorator ; vì vậy PhoneNumberlà một lớp mô hình nhưng PhoneNumber(TableViewRepresentation)là một thể loại xem. Nguồn dữ liệu (một đối tượng điều khiển) làm trung gian giữa mô hình và khung nhìn, do đó kiến ​​trúc MVC vẫn giữ.

Bạn cũng có thể thấy việc sử dụng các danh mục này làm trang trí trong các khung của Apple. NSAttributedStringlà một lớp mô hình, giữ một số văn bản và thuộc tính. AppKit cung cấp NSAttributedString(AppKitAdditions)và UIKit cung cấp NSAttributedString(NSStringDrawing), các danh mục trang trí có thêm hành vi vẽ cho các lớp mô hình này.


Một tên tốt cho lớp đang làm việc như là nguồn dữ liệu và đại biểu xem bảng là gì?
Johan Karlsson

1
@JohanKarlsson Tôi thường chỉ gọi nó là nguồn dữ liệu. Có lẽ hơi cẩu thả, nhưng tôi kết hợp cả hai thường xuyên đủ để biết rằng "nguồn dữ liệu" của tôi là một sự thích ứng với định nghĩa hạn chế hơn của Apple.

1
Bài viết này: objc.io/su-1/table-view.html đề xuất một cách để xử lý nhiều loại ô, theo đó bạn xử lý lớp ô trong cellForPhotoAtIndexPathphương thức của nguồn dữ liệu, sau đó gọi một phương thức xuất xưởng thích hợp. Tất nhiên chỉ có thể nếu các lớp cụ thể dự đoán chiếm các hàng cụ thể. Hệ thống các mô hình thể loại tạo chế độ xem của bạn thực tế thanh lịch hơn nhiều, tôi nghĩ, mặc dù nó có thể là một cách tiếp cận không chính thống đối với MVC! :)
Benji XVI

1
Tôi đã thử demo mẫu này tại github.com/yonglam/TableViewPotype . Hy vọng nó hữu ích cho ai đó.
Andrew

1
Tôi sẽ bỏ phiếu một cách dứt khoát cho cách tiếp cận bộ chọn động. Nó rất nguy hiểm vì các vấn đề chỉ biểu hiện trong thời gian chạy. Không có cách tự động nào để đảm bảo rằng bộ chọn đã cho tồn tại nó được gõ chính xác và cách tiếp cận này cuối cùng sẽ sụp đổ và là một cơn ác mộng cần duy trì. Cách tiếp cận khác, tuy nhiên, rất thông minh.
mkko

3

Mọi người thường có xu hướng đóng gói rất nhiều vào UIViewControll / UITableViewControll.

Ủy quyền cho một lớp khác (không phải bộ điều khiển xem) thường hoạt động tốt. Các đại biểu không nhất thiết cần một tham chiếu trở lại bộ điều khiển xem, vì tất cả các phương thức ủy nhiệm đều được chuyển tham chiếu đến UITableView, nhưng họ sẽ cần truy cập bằng cách nào đó vào dữ liệu mà họ ủy quyền.

Một vài ý tưởng để sắp xếp lại để giảm chiều dài:

  • nếu bạn đang xây dựng các ô xem bảng trong mã, hãy xem xét tải chúng thay vì từ tệp nib hoặc từ bảng phân cảnh. Bảng phân cảnh cho phép các ô nguyên mẫu và bảng tĩnh - kiểm tra các tính năng đó nếu bạn không quen

  • nếu các phương thức ủy nhiệm của bạn chứa nhiều câu lệnh 'if' (hoặc câu lệnh chuyển đổi) đó là một dấu hiệu cổ điển mà bạn có thể thực hiện một số phép tái cấu trúc

Tôi luôn cảm thấy hơi buồn cười khi tôi UITableViewDataSourcechịu trách nhiệm xử lý đúng bit dữ liệu định cấu hình chế độ xem để hiển thị nó. Một điểm tái cấu trúc tốt có thể là thay đổi bạn cellForRowAtIndexPathđể xử lý dữ liệu cần hiển thị trong một ô, sau đó ủy quyền việc tạo chế độ xem ô cho một đại biểu khác (ví dụ: tạo CellViewDelegatehoặc tương tự) được truyền vào mục dữ liệu thích hợp.


Đây là một câu trả lời tốt đẹp. Tuy nhiên, một vài câu hỏi xuất hiện trong đầu tôi. Tại sao bạn tìm thấy rất nhiều câu lệnh if (hoặc câu lệnh chuyển đổi) là thiết kế xấu? Bạn có thực sự có nghĩa là, rất nhiều câu lệnh if và và switch lồng nhau không? Làm thế nào để bạn tái xác định để tránh các câu lệnh if hoặc hoặc switch?
Johan Karlsson

@JohanKarlsson một kỹ thuật là thông qua đa hình. Nếu bạn cần làm một việc với một loại đối tượng và một thứ khác với một loại khác, hãy làm cho các đối tượng đó các lớp khác nhau và để chúng chọn công việc cho bạn.

@GrahamLee Vâng, tôi biết đa hình ;-) Tuy nhiên tôi không chắc chắn làm thế nào để áp dụng nó trong bối cảnh này. Xin hãy giải thích về điều này.
Johan Karlsson

@JohanKarlsson đã hoàn tất;)

2

Đây là những gì tôi đang làm khi gặp vấn đề tương tự:

  • Di chuyển các hoạt động liên quan đến dữ liệu sang lớp XXXDataSource (kế thừa từ BaseDataSource: NSObject). BaseDataSource cung cấp một số phương thức thuận tiện như - (NSUInteger)rowsInSection:(NSUInteger)sectionNum;, lớp con ghi đè phương thức tải dữ liệu (vì các ứng dụng thường có một số phương thức tải bộ nhớ cache giống như - (void)loadDataWithUpdateBlock:(LoadProgressBlock)dataLoadBlock completion:(LoadCompletionBlock)completionBlock;vậy để chúng tôi có thể cập nhật giao diện người dùng với dữ liệu được lưu trong bộ nhớ cache trong LoadProTHERBlock trong khi chúng tôi đang cập nhật thông tin từ mạng và trong khối hoàn thành chúng tôi làm mới UI với dữ liệu mới và loại bỏ các chỉ số progess, nếu có). Những lớp này KHÔNG phù hợp với UITableViewDataSourcegiao thức.

  • Trong BaseTableViewCont điều khiển (phù hợp với UITableViewDataSourceUITableViewDelegategiao thức) tôi có tham chiếu đến BaseDataSource, mà tôi tạo trong khi khởi động trình điều khiển. Trong UITableViewDataSourcemột phần của bộ điều khiển, tôi chỉ cần trả về các giá trị từ dataSource (như - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return [self.tableViewDataSource sectionsCount]; }).

Đây là cellForRow của tôi trong lớp cơ sở (không cần ghi đè trong các lớp con):

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSString *cellIdentifier = [NSString stringWithFormat:@"%@%@", NSStringFromClass([self class]), @"TableViewCell"];
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
    if (!cell) {
        cell = [self createCellForIndexPath:indexPath withCellIdentifier:cellIdentifier];
    }
    [self configureCell:cell atIndexPath:indexPath];
    return cell;
}

configureCell phải được ghi đè bởi các lớp con và createdCell trả về UITableViewCell, vì vậy nếu bạn muốn ô tùy chỉnh, hãy ghi đè lên nó.

  • Sau khi mọi thứ cơ bản được cấu hình (thực ra, trong dự án đầu tiên sử dụng sơ đồ đó, sau đó phần này có thể được sử dụng lại), phần còn lại cho các BaseTableViewControllerlớp con là:

    • Ghi đè configureCell (điều này thường chuyển đổi sang hỏi dataSource cho đối tượng cho đường dẫn chỉ mục và cung cấp nó cho phương thức configureWithXXX: của ô hoặc nhận biểu diễn UITableViewCell của đối tượng như trong câu trả lời của user4051)

    • Ghi đè didSelectRowAtIndexPath: (rõ ràng)

    • Viết lớp con BaseDataSource, đảm nhiệm công việc với phần cần thiết của Mô hình (giả sử có 2 lớp AccountLanguagedo đó, các lớp con sẽ là AccountDataSource và LanguageDataSource).

Và đó là tất cả cho phần xem bảng. Tôi có thể đăng một số mã lên GitHub nếu cần.

Chỉnh sửa: một số đề xuất có thể được tìm thấy tại http://www.objc.io/su-1/lighter-view-controllers.html (có liên kết đến câu hỏi này) và bài viết đồng hành về điều khiển bảng.


2

Quan điểm của tôi về điều này là mô hình cần đưa ra một mảng đối tượng được gọi là ViewModel hoặc viewData được gói gọn trong một cellConfigurator. CellConfigurator giữ CellInfo cần thiết để loại bỏ nó và để cấu hình ô. nó cung cấp cho tế bào một số dữ liệu để tế bào có thể tự cấu hình. điều này cũng hoạt động với phần nếu bạn thêm một số đối tượng PartConfigurator giữ CellConfigurators. Tôi đã bắt đầu sử dụng điều này một thời gian trước, ban đầu chỉ cung cấp cho ô một viewData và có thỏa thuận ViewContoder với việc xử lý ô. nhưng tôi đã đọc một bài viết chỉ vào repo gitHub này.

https://github.com/fastred/ConfigurableTableViewCont điều khiển

điều này có thể thay đổi cách bạn đang tiếp cận điều này.


2

Gần đây tôi đã viết một bài viết về cách triển khai các đại biểu và nguồn dữ liệu cho UITableView: http://gosuwachu.gitlab.io/2014/01/12/uitableview-controll/

Ý tưởng chính là phân chia trách nhiệm thành các lớp riêng biệt, như nhà máy sản xuất tế bào, nhà máy sản xuất và cung cấp một số giao diện chung cho mô hình mà UITableView sẽ hiển thị. Sơ đồ dưới đây giải thích tất cả:

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


Liên kết này không còn hoạt động.
koen

1

Theo nguyên tắc RẮN sẽ giải quyết bất kỳ loại vấn đề như thế này.

Nếu bạn muốn đến các lớp học của bạn để có JUST A SINGLE trách nhiệm, bạn nên xác định riêng biệt DataSourceDelegatecác lớp học và chỉ cần tiêm chúng vào tableViewchủ sở hữu (có thể là UITableViewControllerhay UIViewControllerhoặc bất cứ điều gì khác). Đây là cách bạn vượt qua sự tách biệt của mối quan tâm .

Nhưng nếu bạn chỉ muốn có mã sạch và dễ đọc và muốn thoát khỏi tệp viewControll khổng lồ đó và bạn đang ở Swif , bạn có thể sử dụng extensions cho điều đó. Phần mở rộng của lớp đơn có thể được viết trong các tệp khác nhau và tất cả chúng đều có quyền truy cập lẫn nhau. Nhưng đây là nit thực sự giải quyết vấn đề SoC như tôi đã đề cập.

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.