patternswiftMajor
Rewrite code from Objective-C to conform with Swift power tools and concise style
Viewed 0 times
swiftwithandrewritetoolspowerconcisestyleconformobjective
Problem
I started a project with Objective-C and rewrote it with Swift. The project contains two
MasterViewController has two
Furthermore, when I select a cell in DetailViewController, it displays a checkmark and updates the MasterViewController related cell which triggered the segue.
Here is a picture of the different scenes of the project:
And here is my code:
MasterViewController
```
class MasterViewController: UITableViewController {
var myString = "Yellow"
var myInt = 16
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "attributeChangeMethod:", name: "attributeChange", object: nil)
}
func attributeChangeMethod(notif: NSNotification) {
if let passedString: AnyObject = notif.userInfo?["String"] {
myString = passedString as String
tableView.reloadRowsAtIndexPaths([NSIndexPath(forRow: 0, inSection: 0)], withRowAnimation: .Automatic)
}
if let passedInt: AnyObject = notif.userInfo?["Int"] {
myInt = passedInt as Int
tableView.reloadRowsAtIndexPaths([NSIndexPath(forRow: 1, inSection: 0)], withRowAnimation: .Automatic)
}
}
deinit {
NSNotificationCenter.defaultCenter().removeObserver(self, name: "attributeChange", object:nil)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
overr
UITableViewControllers: MasterViewController and DetailViewController. MasterViewController is linked to DetailViewController with a segue.MasterViewController has two
UITableViewCells. The cell with indexPath [0, 0] has a detailTextLabel that displays a String. Each time I click on this cell, I go to the DetailViewController which, in this case, displays a list of Strings. The cell with indexPath [0, 1] has a detailTextLabel that displays an Int. Each time I click on this cell, I go to the DetailViewController, which in this case, displays a list of Ints.Furthermore, when I select a cell in DetailViewController, it displays a checkmark and updates the MasterViewController related cell which triggered the segue.
Here is a picture of the different scenes of the project:
And here is my code:
MasterViewController
```
class MasterViewController: UITableViewController {
var myString = "Yellow"
var myInt = 16
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "attributeChangeMethod:", name: "attributeChange", object: nil)
}
func attributeChangeMethod(notif: NSNotification) {
if let passedString: AnyObject = notif.userInfo?["String"] {
myString = passedString as String
tableView.reloadRowsAtIndexPaths([NSIndexPath(forRow: 0, inSection: 0)], withRowAnimation: .Automatic)
}
if let passedInt: AnyObject = notif.userInfo?["Int"] {
myInt = passedInt as Int
tableView.reloadRowsAtIndexPaths([NSIndexPath(forRow: 1, inSection: 0)], withRowAnimation: .Automatic)
}
}
deinit {
NSNotificationCenter.defaultCenter().removeObserver(self, name: "attributeChange", object:nil)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
overr
Solution
The problem here isn't that your Swift looks like Objective-C. The problem is that, because you're developing a toy app, you've used a toy design, and now you don't like the toy design. Probably you followed a tutorial you found on the Internet (or even in Apple's docs), and most of those are for toy apps and toy designs. In those tutorials, the author hates adding classes, and ends up littering
Here's how I would design this app as if it were not a toy. Notice that in the design below, there are almost no
Objective-C
I know your question was about Swift, but I'll start with Objective-C to demonstrate that the problem is with the toy design, not the language choice.
First, let's create a real model layer. We have a master model, which is an array of details:
MasterModel.h
and we have a detail model:
DetailModel.h
In a real app, you'd probably load the model from a database or a file. In this toy app, I'll hard-code it:
MasterModel.m
DetailModel.m
Now we need a master view controller to display the master model:
MasterViewController.h
MasterViewController.m
The master table view shows one row per detail model in the master model:
I use a custom cell class for each row. I just hand the cell its model and the cell takes care of the details:
In a real app, you might have many segues, so we dispatch to another method based on the segue identifier:
Because each master cell knows which detail model it's displaying, we can just ask the cell for its model and hand that off to the detail view controller:
MasterCell.h
MasterCell.m
Remember that cells can be reused. In this app, they won't be (because there are only going to be two instances of
```
if statements all over the code. But fewer classes does not necessarily lead to better design.Here's how I would design this app as if it were not a toy. Notice that in the design below, there are almost no
if statements. In serious apps, I try to rely on message dispatch to choose the code path.Objective-C
I know your question was about Swift, but I'll start with Objective-C to demonstrate that the problem is with the toy design, not the language choice.
First, let's create a real model layer. We have a master model, which is an array of details:
MasterModel.h
#import
@interface MasterModel : NSObject
/** An array of `DetailModel`. */
@property (nonatomic, strong, readonly) NSArray *details;
@endand we have a detail model:
DetailModel.h
#import
@interface DetailModel : NSObject
@property (nonatomic, copy, readonly) NSString *title;
@property (nonatomic, copy, readonly) NSArray *options;
@property (nonatomic) NSUInteger selectedOptionIndex;
- (instancetype)initWithTitle:(NSString *)title options:(NSArray *)options;
@endIn a real app, you'd probably load the model from a database or a file. In this toy app, I'll hard-code it:
MasterModel.m
#import "MasterModel.h"
#import "DetailModel.h"
@implementation MasterModel
- (instancetype)init {
if (self = [super init]) {
_details = @[ [self newStringDetailModel], [self newIntDetailModel] ];
}
return self;
}
- (DetailModel *)newStringDetailModel {
NSArray *options = @[ @"Yellow", @"Green", @"Blue", @"Red" ];
return [[DetailModel alloc] initWithTitle:@"My String" options:options];
}
- (DetailModel *)newIntDetailModel {
NSArray *options = @[ @8, @16, @32, @64 ];
return [[DetailModel alloc] initWithTitle:@"My Int" options:options];
}
@endDetailModel.m
#import "DetailModel.h"
@implementation DetailModel
- (instancetype)initWithTitle:(NSString *)title options:(NSArray *)options {
if (self = [super init]) {
_title = [title copy];
_options = [options copy];
}
return self;
}
@endNow we need a master view controller to display the master model:
MasterViewController.h
#import
@class MasterModel;
@interface MasterViewController : UITableViewController
@property (nonatomic, strong, readonly) MasterModel *model;
@endMasterViewController.m
#import "MasterViewController.h"
#import "MasterModel.h"
#import "MasterCell.h"
#import "DetailViewController.h"
@interface MasterViewController ()
@end
@implementation MasterViewController
- (void)awakeFromNib {
[super awakeFromNib];
_model = [[MasterModel alloc] init];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}The master table view shows one row per detail model in the master model:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return _model.details.count;
}I use a custom cell class for each row. I just hand the cell its model and the cell takes care of the details:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
MasterCell *cell = [tableView dequeueReusableCellWithIdentifier:@"detail" forIndexPath:indexPath];
cell.model = _model.details[indexPath.row];
return cell;
}In a real app, you might have many segues, so we dispatch to another method based on the segue identifier:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:@"ShowDetail"]) {
[self prepareForShowDetailSegue:segue sender:sender];
}
}Because each master cell knows which detail model it's displaying, we can just ask the cell for its model and hand that off to the detail view controller:
- (void)prepareForShowDetailSegue:(UIStoryboardSegue *)segue sender:(id)sender {
DetailViewController *destination = segue.destinationViewController;
MasterCell *cell = sender;
destination.model = cell.model;
}
@endMasterCell.h
#import
@class DetailModel;
@interface MasterCell : UITableViewCell
@property (nonatomic, strong) DetailModel *model;
@endMasterCell.m
#import "MasterCell.h"
#import "DetailModel.h"
@implementation MasterCellRemember that cells can be reused. In this app, they won't be (because there are only going to be two instances of
MasterCell and they fit on the screen together), but this is the general pattern I follow when I expect cell reuse:```
- (void)setModel:(DetailModel *)model {
Code Snippets
#import <Foundation/Foundation.h>
@interface MasterModel : NSObject
/** An array of `DetailModel`. */
@property (nonatomic, strong, readonly) NSArray *details;
@end#import <Foundation/Foundation.h>
@interface DetailModel : NSObject
@property (nonatomic, copy, readonly) NSString *title;
@property (nonatomic, copy, readonly) NSArray *options;
@property (nonatomic) NSUInteger selectedOptionIndex;
- (instancetype)initWithTitle:(NSString *)title options:(NSArray *)options;
@end#import "MasterModel.h"
#import "DetailModel.h"
@implementation MasterModel
- (instancetype)init {
if (self = [super init]) {
_details = @[ [self newStringDetailModel], [self newIntDetailModel] ];
}
return self;
}
- (DetailModel *)newStringDetailModel {
NSArray *options = @[ @"Yellow", @"Green", @"Blue", @"Red" ];
return [[DetailModel alloc] initWithTitle:@"My String" options:options];
}
- (DetailModel *)newIntDetailModel {
NSArray *options = @[ @8, @16, @32, @64 ];
return [[DetailModel alloc] initWithTitle:@"My Int" options:options];
}
@end#import "DetailModel.h"
@implementation DetailModel
- (instancetype)initWithTitle:(NSString *)title options:(NSArray *)options {
if (self = [super init]) {
_title = [title copy];
_options = [options copy];
}
return self;
}
@end#import <UIKit/UIKit.h>
@class MasterModel;
@interface MasterViewController : UITableViewController
@property (nonatomic, strong, readonly) MasterModel *model;
@endContext
StackExchange Code Review Q#62958, answer score: 32
Revisions (0)
No revisions yet.