I plan on including this work in a portfolio.
Question: Will this code get me hired or laughed at?
More Specifically:
How would you rate the general complexity of the code?
How bad does the code smell?
Is the VC too fat?
Glaring best practices mistakes?
Really absolutely any feedback would be appreciated. Thanks
Quick follow-up to Watson's reply:
1) Can you expand on what you said about magic numbers? (Totally missed the bool/BOOL usually I'm better than that. The names w/2 are just temp renames for SO)
2)I have a lot of .h files because I tried to refactor somethings, like animation and URL methods, into helpers, presumably to increase readability. Did I go overboard?
3) In theory, the way the program is setup, the MasterVC should never be deallocated. VC's are just pushed on top, not more than one layer at a time. Is it still necessary to unsubscribe from notifications?
4) Lastly, on a scale of 1-10 what is your impression of the code overall? (from what you've seen and discounting the name and bool problem)
Details: iOS: 6 (updating to 7 currently) | xcode: 4.6 | tested: iphone 4 device | ARC Enabled
MasterViewController.h
#import <UIKit/UIKit.h>
#import "SRAPI.h"
#import "SRChoiceBox.h"
#import "SRPostTopic.h"
#import "SRDetailViewController.h"
#import "SRCollapsibleCell.h"
#import "SRAnimationHelper.h"
#import "SROpenTokVideoHandler.h"
#import "SRObserveViewController.h"
@interface SRMasterViewController2 : UIViewController <UITableViewDelegate, UITableViewDataSource, SRChoiceBoxDelegate, SRPostTopicDelegate>
@property (weak, nonatomic) IBOutlet UITableView *topicsTableView;
@property (weak, nonatomic) IBOutlet UIView *postTopicContainer;
@property (weak, nonatomic) IBOutlet UILabel *statusLabel;
@property (strong, nonatomic) NSIndexPath *openCellIndex;
@property (strong, nonatomic) RKPaginator *paginator;
@property (strong, nonatomic) SROpenTokVideoHandler *openTokHandler;
@end
MasterViewController.m
#import "SRMasterViewController2.h"
#import "UIScrollView+SVPullToRefresh.h"
#import "UIScrollView+SVInfiniteScrolling.h"
#import "SRUrlHelper.h"
#import "SRNavBarHelper.h"
@interface SRMasterViewController2 ()
@property NSInteger offset;
@property NSInteger totalPages;
@property NSMutableArray *topicsArray;
@property bool isPaginatorLoading;
@end
@implementation SRMasterViewController2
- (void)viewDidLoad {
[super viewDidLoad];
[self configureTableView];
[self configureNavBar];
[self configurePostTopicContainer];
[SRAPI sharedInstance];
[self paginate];
self.openTokHandler = [SROpenTokVideoHandler new];
[self configureNotifications];
}
- (void)configureNotifications {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receiveNotifications:) name:kFetchNewTopicsAndReloadTableData object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receiveNotifications:) name:kFetchRoomFromUrl object:nil];
}
- (void)receiveNotifications:(NSNotification *)notificaiton {
if ([notificaiton.name isEqualToString:kFetchRoomFromUrl]) {
NSURL *url = notificaiton.userInfo[@"url"];
[self fetchRoomWithUrl:url];
}
else if ([notificaiton.name isEqualToString:kFetchNewTopicsAndReloadTableData]) {
self.offset = 1;
[self.topicsTableView.infiniteScrollingView startAnimating];
[self paginate];
}
}
- (void)fetchRoomWithUrl:(NSURL *)url {
NSDictionary *dict = [SRUrlHelper parseQueryString:[url query]];
SRRoom *room = [[SRRoom alloc] init];
room.position = (NSString *)[url pathComponents][3];
room.topicId = [url pathComponents][2];
room.sessionId = dict[@"sessionID"];
[self performSegueWithIdentifier:@"showDetail2" sender:room];
}
- (void)configureNavBar {
//Button displays container for posting topics
UIBarButtonItem *rightPostTopicButton =
[SRNavBarHelper buttonForNavBarWithImage:[UIImage imageNamed:@"logo"]
highlightedImage:nil
selector:@selector(showPostTopicContainer)
target:self];
self.navigationItem.rightBarButtonItem = rightPostTopicButton;
//suffle button - Joins random room
UIBarButtonItem *leftShuffleButton =
[SRNavBarHelper buttonForNavBarWithImage:[UIImage imageNamed:@"shuffle.png"]
highlightedImage:[UIImage imageNamed:@"shufflePressed.png"]
selector:@selector(joinRandomRoom)
target:self];
self.navigationItem.leftBarButtonItem = leftShuffleButton;
}
- (void)joinRandomRoom {
NSMutableArray *activeTopics = [NSMutableArray new];
SRTopic *randomTopic = [SRTopic new];
SRRoom *randomRoom = [SRRoom new];
//find topics with people in them
for (SRTopic *topic in self.topicsArray) {
if ([topic.agreeDebaters integerValue] > 0 || [topic.disagreeDebaters integerValue] > 0) {
[activeTopics addObject:topic];
}
}
int numberOfActiveTopics = activeTopics.count;
if (numberOfActiveTopics > 0) {
//Put user into a random Active Room
int random = arc4random() % numberOfActiveTopics;
randomTopic = (SRTopic *)activeTopics[random];
if (randomTopic.agreeDebaters.intValue > randomTopic.disagreeDebaters.intValue) {
randomRoom.position = @"disagree";
}
else if (randomTopic.agreeDebaters.intValue < randomTopic.disagreeDebaters.intValue) {
randomRoom.position = @"agree";
}
else {
randomRoom.position = [self randomlyChooseAgreeDisagree];
}
}
else {
//No Active Rooms, put user in a random room
int random = arc4random() % self.topicsArray.count;
randomTopic = (SRTopic *)self.topicsArray[random];
randomRoom.position = [self randomlyChooseAgreeDisagree];
}
randomRoom.topicId = randomTopic.topicId;
[self performSegueWithIdentifier:@"showDetail2" sender:randomRoom];
}
- (NSString *)randomlyChooseAgreeDisagree {
int r = arc4random() % 2;
return (r == 0) ? @"agree" : @"disagree";
}
- (void)configureTableView {
//set offset for loading tabledata
self.offset = 1;
//add pull to refresh controls
UIRefreshControl *refreshControl = [[UIRefreshControl alloc] init];
[refreshControl addTarget:self action:@selector(refresh:) forControlEvents:UIControlEventValueChanged];
[self.topicsTableView addSubview:refreshControl];
//add infinite scrolling
[self addInfiniteScrolling:self.topicsTableView];
//close all cells
self.openCellIndex = nil;
//Smooth scrolling
self.topicsTableView.layer.shouldRasterize = YES;
self.topicsTableView.layer.rasterizationScale = [[UIScreen mainScreen] scale];
}
- (void)configurePostTopicContainer {
//configure container for posting topics
SRPostTopic *postTopic = [[SRPostTopic alloc]initWithFrame:CGRectMake(0, 0, 320, 133)];
[self.postTopicContainer addSubview:postTopic];
postTopic.delegate = self;
}
- (void)addInfiniteScrolling:(UITableView *)tableView {
[tableView addInfiniteScrollingWithActionHandler: ^(void) {
self.offset += 1;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self paginate];
double delayInSeconds = 0.8;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void) {
[self.topicsTableView.infiniteScrollingView stopAnimating];
});
});
}];
//configure infinite scrolling style
self.topicsTableView.infiniteScrollingView.activityIndicatorViewStyle = UIActivityIndicatorViewStyleWhiteLarge;
}
- (void)refresh:(UIRefreshControl *)refreshControl {
self.offset = 1;
self.openCellIndex = nil;
//stop refresh after successful AJAX call for topics
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self paginate];
double delayInSeconds = 1;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void) {
[refreshControl endRefreshing];
});
});
}
- (void)paginate {
// Create weak reference to self to use within the paginators completion block
__weak typeof(self) weakSelf = self;
// Setup paginator
if (!self.paginator) {
self.paginator.perPage = 20;
NSString *requestString = [NSString stringWithFormat:@"?page=:currentPage&per_page=:perPage"];
self.paginator = [[RKObjectManager sharedManager] paginatorWithPathPattern:requestString];
[self.paginator setCompletionBlockWithSuccess: ^(RKPaginator *paginator, NSArray *objects, NSUInteger page) {
NSMutableArray *topicsArrayTemp = [objects mutableCopy];
weakSelf.isPaginatorLoading = NO;
if (weakSelf.offset == 1) {
[weakSelf replaceRowsInTableView:topicsArrayTemp];
}
else {
[weakSelf insertRowsInTableView:topicsArrayTemp];
}
[weakSelf.topicsTableView.infiniteScrollingView stopAnimating];
} failure: ^(RKPaginator *paginator, NSError *error) {
weakSelf.isPaginatorLoading = NO;
[weakSelf.topicsTableView.infiniteScrollingView stopAnimating];
[weakSelf.self noResults];
}];
}
if (!weakSelf.isPaginatorLoading) {
weakSelf.isPaginatorLoading = YES;
[self.paginator loadPage:self.offset];
}
}
- (void)noResults {
double delayInSeconds = 0.6;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void) {
[self performSegueWithIdentifier:@"noResults" sender:nil];
});
}
#pragma mark - Posting a new topic
//open/close container for posting topics
- (void)showPostTopicContainer {
[self.view endEditing:YES];
CGRect newTableViewFrame = self.topicsTableView.frame;
CGRect newPostTopicFrame = self.postTopicContainer.frame;
float duration, alpha;
if ([self isPostTopicContainerOpen]) {
newTableViewFrame.origin.y -= 133;
newPostTopicFrame.origin.y -= 133;
duration = .3;
alpha = 0;
}
else {
newTableViewFrame.origin.y += 133;
newPostTopicFrame.origin.y += 133;
duration = .4;
alpha = 1;
}
[UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionCurveEaseInOut animations: ^{
self.postTopicContainer.alpha = alpha;
self.topicsTableView.frame = newTableViewFrame;
self.postTopicContainer.frame = newPostTopicFrame;
} completion:nil];
}
- (BOOL)isPostTopicContainerOpen {
return (self.postTopicContainer.frame.origin.y < 0) ? NO : YES;
}
//update fading status UILabel at the bottom of the screen
- (void)statusUpdate:(NSString *)message {
self.statusLabel.text = message;
[self.statusLabel.layer addAnimation:[SRAnimationHelper fadeOfSRMasterViewStatusLabel] forKey:nil];
}
//Post a new Topic to the Server
- (void)postTopicButtonPressed:(NSString *)contents {
//set up params
NSDictionary *newTopic = @{ @"topic":contents };
//send new topic posting
[[RKObjectManager sharedManager] postObject:nil path:@"topics/new" parameters:newTopic success: ^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
if ([self isPostTopicContainerOpen]) {
//close post box if it's open
[self showPostTopicContainer];
}
[self statusUpdate:@"Topic Posted!"];
} failure: ^(RKObjectRequestOperation *operation, NSError *error) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Oops"
message:@"We weren't able to post your shout. Try again soon!"
delegate:nil
cancelButtonTitle:@"Sure"
otherButtonTitles:nil, nil];
[alert show];
}];
}
//Delegate for SRChoiceBox - user chooses Agree/Disagree/Observe
- (void)positionWasChoosen:(NSString *)choice topicId:(NSNumber *)topicId {
SRRoom *room = [[SRRoom alloc] init];
room.position = choice;
room.topicId = topicId;
if ([choice isEqualToString:@"observe"]) {
[self performSegueWithIdentifier:@"showObserve" sender:room];
}
else {
[self performSegueWithIdentifier:@"showDetail2" sender:room];
}
}
- (void)segueToRoomWithTopicID:(NSNumber *)topicId andPosition:(NSString *)choice {
SRRoom *room = [[SRRoom alloc] init];
room.position = choice;
room.topicId = topicId;
if ([choice isEqualToString:@"observe"]) {
[self performSegueWithIdentifier:@"showObserve" sender:room];
}
else {
[self performSegueWithIdentifier:@"delete" sender:nil];
}
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
//close Post Topic Container
if ([self isPostTopicContainerOpen]) {
[self showPostTopicContainer];
}
if ([[segue identifier] isEqualToString:@"showDetail2"] || [[segue identifier] isEqualToString:@"showObserve"]) {
if (self.openTokHandler) {
[self.openTokHandler safetlyCloseSession];
}
[[segue destinationViewController] setOpenTokHandler:self.openTokHandler];
[[segue destinationViewController] setRoom:sender];
}
sender = nil;
}
#pragma mark - UITABLEVIEW
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [self.topicsArray count];
}
- (void)insertRowsInTableView:(NSMutableArray *)topics {
if (topics.count < 1) {
[self noNewResults];
return;
}
NSMutableArray *temp = [NSMutableArray new];
int lastRowNumber = [self.topicsTableView numberOfRowsInSection:0] - 1;
for (SRTopic *topic in topics) {
if (![self.topicsArray containsObject:topic]) {
[self.topicsArray addObject:topic];
NSIndexPath *ip = [NSIndexPath indexPathForRow:lastRowNumber inSection:0];
[temp addObject:ip];
++lastRowNumber;
}
}
[self.topicsTableView beginUpdates];
[self.topicsTableView insertRowsAtIndexPaths:temp
withRowAnimation:UITableViewRowAnimationTop];
[self.topicsTableView endUpdates];
if (temp.count == 0) {
[self noNewResults];
}
}
- (void)noNewResults {
int lastRowNumber = [self.topicsTableView numberOfRowsInSection:0] - 1;
[self statusUpdate:@"No New Topics. Check Back Soon!"];
[self.topicsTableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:lastRowNumber - 6 inSection:0] atScrollPosition:UITableViewScrollPositionTop animated:YES];
self.offset--;
}
- (void)replaceRowsInTableView:(NSMutableArray *)topics {
self.topicsArray = topics;
[UIView animateWithDuration:.3 delay:.5 options:UIViewAnimationOptionCurveEaseInOut animations: ^{
self.topicsTableView.layer.opacity = 0;
} completion: ^(BOOL finished) {
self.topicsTableView.layer.opacity = 1;
[[self.topicsTableView layer] addAnimation:[SRAnimationHelper tableViewReloadDataAnimation] forKey:@"UITableViewReloadDataAnimationKey"];
[self.topicsTableView reloadData];
}];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *CellIdentifier2 = @"SRCollapsibleCellClosed";
SRCollapsibleCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier2];
if (cell == nil) {
cell = [[SRCollapsibleCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier2];
}
SRTopic *topic = [self.topicsArray objectAtIndex:indexPath.row];
[cell updateWithTopic:topic];
if ([self isCellOpen:indexPath]) {
CGAffineTransform transformation = CGAffineTransformMakeRotation(M_PI / 2);
cell.arrow.transform = transformation;
if (![self hasChoiceBox:cell]) {
[self insertChoiceBox:cell atIndex:indexPath];
}
}
else {
CGAffineTransform transformation = CGAffineTransformMakeRotation(0);
cell.arrow.transform = transformation;
}
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if ([self isCellOpen:indexPath]) {
[self closeCellAtIndexPath:indexPath];
}
else {
NSIndexPath *openCell = self.openCellIndex;
NSIndexPath *newOpenCell = indexPath;
[self closeCellAtIndexPath:openCell];
[self openCellAtIndexPath:newOpenCell];
}
[tableView beginUpdates];
[tableView endUpdates];
[tableView deselectRowAtIndexPath:indexPath animated:NO];
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
if ([indexPath isEqual:self.openCellIndex]) {
return 217.0;
}
else {
return 63.0;
}
}
- (void)rotateCellArrowAtIndexPath:(NSIndexPath *)indexPath willOpen:(bool)willOpen animated:(bool)animated {
// Change Arrow orientation
SRCollapsibleCell *cell = (SRCollapsibleCell *)[self.topicsTableView cellForRowAtIndexPath:indexPath];
CGAffineTransform transformation;
if (willOpen) {
transformation = CGAffineTransformMakeRotation(M_PI / 2);
}
else {
transformation = CGAffineTransformMakeRotation(0);
}
if (animated) {
[UIView animateWithDuration:.2 delay:0 options:UIViewAnimationOptionCurveLinear animations: ^{
cell.arrow.transform = transformation;
} completion:nil];
}
else {
cell.arrow.transform = transformation;
}
}
- (BOOL)isCellOpen:(NSIndexPath *)indexPath {
return [indexPath isEqual:self.openCellIndex];
}
- (void)closeCellAtIndexPath:(NSIndexPath *)indexPath {
[self rotateCellArrowAtIndexPath:indexPath willOpen:NO animated:YES];
[self removeSRChoiceBoxFromCellAtIndexPath:indexPath];
self.openCellIndex = nil;
}
- (void)openCellAtIndexPath:(NSIndexPath *)indexPath {
[self rotateCellArrowAtIndexPath:indexPath willOpen:YES animated:YES];
SRCollapsibleCell *cell = (SRCollapsibleCell *)[self.topicsTableView cellForRowAtIndexPath:indexPath];
[self insertChoiceBox:cell atIndex:indexPath];
self.openCellIndex = indexPath;
}
- (void)removeSRChoiceBoxFromCellAtIndexPath:(NSIndexPath *)indexPath {
SRCollapsibleCell *cell = (SRCollapsibleCell *)[self.topicsTableView cellForRowAtIndexPath:indexPath];
for (id subview in cell.SRCollapsibleCellContent.subviews) {
if ([subview isKindOfClass:[SRChoiceBox class]]) {
[subview removeFromSuperview];
}
}
}
- (void)insertChoiceBox:(SRCollapsibleCell *)cell atIndex:(NSIndexPath *)indexPath {
SRChoiceBox *newBox = [[SRChoiceBox alloc] initWithFrame:CGRectMake(0, 0, 310, 141)];
SRTopic *topic = [self.topicsArray objectAtIndex:indexPath.row];
[newBox updateWithSRTopic:topic];
newBox.delegate = self;
[cell.SRCollapsibleCellContent addSubview:newBox];
}
- (bool)hasChoiceBox:(SRCollapsibleCell *)cell {
for (UIView *subview in cell.SRCollapsibleCellContent.subviews) {
if ([subview isKindOfClass:[SRChoiceBox class]]) {
return true;
}
}
return false;
}
@end