3
\$\begingroup\$

This is my first app I have made after learning programming on iOS. Most of the app is complete just the UI and some features are left to be added.

Any optimizations and coding practices would be very helpful.

The code can be found on my github repo
Download link for zip if there are some problems with github version:

Here are the main files
ACViewController.h

#import <UIKit/UIKit.h>
#import "ACAddTaskViewController.h"
#import "ACCategory.h"

@interface ACViewController : UIViewController <UITableViewDataSource, UITableViewDelegate, ACAddTaskViewControllerDelegate, ACSelectCategoryViewControllerDelegate, MGSwipeTableCellDelegate>

@property (strong, nonatomic) IBOutlet UITableView *tableView;
@property (strong, nonatomic) NSMutableArray *tasks;
@property (strong, nonatomic) NSMutableArray *visibleTasks;
@property (strong, nonatomic) NSMutableArray *categories;
@property (strong, nonatomic) ACCategory *category;
@property (strong, nonatomic) IBOutlet UIButton *selectCategoryButton;
@property (strong, nonatomic) IBOutlet UIButton *addNewButton;
@property (strong, nonatomic) IBOutlet UITextField *addTaskTextField;
@property (strong, nonatomic) IBOutlet UIScrollView *menuBarScrollView;

@end

ACViewController.m

#import "ACViewController.h"
#import "ACTask.h"
#import "ACTableViewCell.h"
#import "MGSwipeButton.h"
#import "UIApplication+CoreData.h"


@interface ACViewController ()

@property (nonatomic) BOOL didSelectTaskForEditing;
@property (nonatomic) int selectedIndex;
@property (strong, nonatomic) NSMutableArray *arrayOfSortedDates;
@property (strong, nonatomic) NSMutableArray *dates;
@property (strong, nonatomic) NSDateFormatter *dateFormatter;

@end


@implementation ACViewController

-(void)viewDidLoad
{
    [super viewDidLoad];

    self.tableView.dataSource = self;
    self.tableView.delegate = self;
    self.tableView.estimatedRowHeight = 54.0;
    self.tableView.rowHeight = UITableViewAutomaticDimension;
    [self performFirstRunSetup];
    self.categories = [[ACCategory fetchCategories] mutableCopy];
    self.dates = [[ACDueDate fetchDueDates] mutableCopy];
    self.tasks = [[ACTask fetchTasks] mutableCopy];
    self.category = [self.categories objectAtIndex:0];
    [self.selectCategoryButton setTitle:self.category.name forState:UIControlStateNormal];
    [self arrangeTasks];
}

-(void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
}

-(void)performFirstRunSetup
{
    NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
    if (![userDefaults valueForKey:@"isFirstRun"])
    {
        ACCategory *overview = [ACCategory insertCategoryWithName:@"Overview" color:nil serial:0];
        ACCategory *inbox = [ACCategory insertCategoryWithName:@"Inbox" color:nil serial:1];
        ACCategory *home = [ACCategory insertCategoryWithName:@"Home" color:nil serial:2];
        ACCategory *work = [ACCategory insertCategoryWithName:@"Work" color:nil serial:3];
        ACCategory *shopping = [ACCategory insertCategoryWithName:@"Shopping" color:nil serial:4];
        [ACCategory saveCategories];
        [userDefaults setBool:YES forKey:@"isFirstRun"];
    }
}


#pragma mark TableView Delegate

-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    if ([self.category.name isEqualToString:@"Overview"])
    {
        return [[self.visibleTasks objectAtIndex:section] count];
    }
    else
    {
        return [self.visibleTasks count];
    }
}

-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    if ([self.category.name isEqualToString:@"Overview"])
    {
        return [self.visibleTasks count];
    }
    else
    {
        return 1;
    }
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    ACTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell" forIndexPath:indexPath];
    ACTask *task;
    if ([self.category.name isEqualToString:@"Overview"])
    {
        task = [[self.visibleTasks objectAtIndex:indexPath.section] objectAtIndex:indexPath.row];
    }
    else
    {
        task = [self.visibleTasks objectAtIndex:indexPath.row];
    }
    cell.taskText.text = task.name;
    cell.taskDate.text = task.dueDate.date;
    cell.categoryName.text = task.category.name;
    cell.categoryColor.image = [[UIImage imageNamed:@"categoryColorImage.png"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
    cell.categoryColor.tintColor = task.category.color;
    cell.delegate = self;
    return cell;
}

-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    [self.tableView deselectRowAtIndexPath:indexPath animated:TRUE];
    [self performSegueWithIdentifier:@"pushToAddTaskViewController" sender:nil];
}

-(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
    if ([self.category.name isEqualToString:@"Overview"])
    {
        return [(ACDueDate *)[self.dates objectAtIndex:section] date];
    }
    else
    {
        return nil;
    }
}

-(CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
    if (section == 0)
    {
        return  30.0;
    }
    else
    {
        return 12.0;
    }
}


#pragma mark ACAddTaskViewControllerDelegate

-(void)taskAddingCancelled
{
    [self dismissViewControllerAnimated:TRUE completion:nil];
}

-(void)taskAdded:(ACTask *)task isEditing:(BOOL)editing categoriesList:(NSMutableArray *)categories dates:(NSArray *)dates
{
    if (([self.categories count]) != [categories count])
    {
        self.categories = categories;
        [self.tableView reloadData];

    }
    self.dates = [dates mutableCopy];
    [self.dates sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"date" ascending:YES]]];
    [self.tasks addObject:task];
    [self arrangeTasks];

    [self.tableView reloadData];
    [self dismissViewControllerAnimated:TRUE completion:nil];
}


#pragma mark ACSelectCategoryToSortViewControllerDelegate

-(void)selectedCategory:(ACCategory *)category categories:(NSArray *)categories
{
    [self dismissViewControllerAnimated:YES completion:nil];
    if ([self.categories count] != [categories count])
    {
        [self.categories removeAllObjects];
        self.categories = [categories mutableCopy];
    }
    if (self.category != category)
    {
        self.category = category;
        [self.selectCategoryButton setTitle:self.category.name forState:UIControlStateNormal];
        [self arrangeTasks];
    }
}

-(void)didCancelSelectingCategory:(NSArray *)categories
{
    [self dismissViewControllerAnimated:YES completion:nil];
    if ([self.categories count] != [categories count])
    {
        [self.categories removeAllObjects];
        [self.categories addObjectsFromArray:categories];
    }
}


#pragma mark - Navigation

-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if ([segue.identifier isEqualToString:@"pushToAddTaskViewController"])
    {
        if ([sender isMemberOfClass:[UIBarButtonItem class]])
        {
        UINavigationController *navigationController = segue.destinationViewController;
        ACAddTaskViewController *addTaskViewController = (ACAddTaskViewController *)navigationController.topViewController;
        addTaskViewController.delegate = self;
        addTaskViewController.categories = self.categories;
        addTaskViewController.dates = self.dates;
        }
    }
    if ([segue.identifier isEqualToString:@"pushToSelectCategoryViewController"])
    {
        UINavigationController *navigationController = segue.destinationViewController;
        ACSelectCategoryViewController *selectCategoryViewController = (ACSelectCategoryViewController *)navigationController.topViewController;
        selectCategoryViewController.categories = [self.categories mutableCopy];
        selectCategoryViewController.delegate = self;
    }
}


#pragma mark Arrange Tasks

-(void)arrangeTasks
{
    if ([self.category.name isEqualToString:@"Overview"])
    {
        [self arrangeIntoSectionsByDate];
        [self.tableView reloadData];
    }
    else
    {
    self.visibleTasks = [self.tasks mutableCopy];
    [self arrangeByCategory];
    [self arrangeByPriority];
    [self arrangeByDate];
    [self.tableView reloadData];

    }
}

-(void)arrangeByCategory
{
    NSPredicate  *filterByCategory = [NSPredicate predicateWithFormat:@"category.name CONTAINS %@", self.category.name];
    self.visibleTasks = [[self.tasks filteredArrayUsingPredicate:filterByCategory] mutableCopy];
}

-(void)arrangeByPriority
{
    [self.visibleTasks sortUsingDescriptors:@[ [NSSortDescriptor sortDescriptorWithKey:@"priority" ascending:NO] ]];
}

-(void)arrangeByDate
{
    [self.visibleTasks sortUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"dueDate.date" ascending:YES]]];
}

-(void)arrangeIntoSectionsByDate
{
    [self.visibleTasks removeAllObjects];
    for (ACDueDate *date in self.dates)
    {
        NSPredicate *filterByDatePredicate = [NSPredicate predicateWithFormat:@"dueDate.date CONTAINS %@", date.date];
        [self.visibleTasks addObject:[self.tasks filteredArrayUsingPredicate:filterByDatePredicate]];
    }
}


#pragma mark Swipe Delegate

-(BOOL)swipeTableCell:(MGSwipeTableCell *)cell canSwipe:(MGSwipeDirection)direction;
{
    return YES;
}

-(NSArray *)swipeTableCell:(MGSwipeTableCell *)cell swipeButtonsForDirection:(MGSwipeDirection)direction
             swipeSettings:(MGSwipeSettings *)swipeSettings
         expansionSettings:(MGSwipeExpansionSettings *)expansionSettings
{
    swipeSettings.transition = MGSwipeTransitionClipCenter;
    swipeSettings.keepButtonsSwiped = NO;
    expansionSettings.buttonIndex = 0;
    expansionSettings.threshold = 1.0;
    expansionSettings.expansionLayout = MGSwipeExpansionLayoutCenter;
    expansionSettings.triggerAnimation.easingFunction = MGSwipeEasingFunctionCubicOut;
    expansionSettings.fillOnTrigger = NO;
    UIColor *greenColor = [UIColor colorWithRed:(33 / 255.0) green:(175 / 255.0) blue:(67 / 255.0) alpha:1.0];
    UIColor *redColor = [UIColor colorWithRed:1.0 green:(59 / 255.0) blue:(50 / 255.0) alpha:1.0];
    if (direction == MGSwipeDirectionLeftToRight)
    {
        MGSwipeButton *deleteButton = [MGSwipeButton buttonWithTitle:@"Delete" backgroundColor:redColor padding:15 callback:^BOOL(MGSwipeTableCell *sender)
        {
            return YES;
        }];
        return @[ deleteButton ];
    }
    else if (direction == MGSwipeDirectionRightToLeft)
    {

        MGSwipeButton *completedButton = [MGSwipeButton buttonWithTitle:@"Completed" backgroundColor:greenColor padding:15 callback:^BOOL(MGSwipeTableCell *sender) {
            return YES;
        }];
        return @[ completedButton ];
    }
    return nil;
}


#pragma mark Lazy Initialization

-(NSDateFormatter *)dateFormatter
{
    if (!_dateFormatter)
    {
        _dateFormatter = [[NSDateFormatter alloc] init];
        _dateFormatter.dateStyle = NSDateFormatterMediumStyle;
    }
    return  _dateFormatter;
}

-(NSMutableArray *)visibleTasks
{
    if (!_visibleTasks) _visibleTasks = [[NSMutableArray alloc] init];
        return _visibleTasks;
}

@end

ACAddTaskViewController.h

#import <UIKit/UIKit.h>
#import "ACSelectCategoryViewController.h"
#import "ACCategory.h"
#import "ACPriorityPickerTableViewCell.h"
#import "ACPriority.h"
#import "ACReminderDateTableViewCell.h"
#import "ACCategoryTableViewCell.h"
#import "ACDueDatePickerCell.h"
#import "ACDueDateTableViewCell.h"
#import "ACDueDate.h"

@protocol ACAddTaskViewControllerDelegate <NSObject, MGSwipeTableCellDelegate>

@required
-(void)taskAddingCancelled;
-(void)taskAdded:(ACTask *)task isEditing:(BOOL)editing categoriesList:(NSMutableArray *)categories dates:(NSArray *)dates;

@end


@interface ACAddTaskViewController : UIViewController <UITableViewDataSource, UITableViewDelegate, UITextFieldDelegate, ACSelectCategoryViewControllerDelegate>

@property (weak, nonatomic) id <ACAddTaskViewControllerDelegate> delegate;
@property (strong, nonatomic) IBOutlet UITableView *tableView;
@property (strong, nonatomic) ACTask *task;
@property (strong, nonatomic) NSMutableArray *categories;
@property (strong, nonatomic) NSMutableArray *dates;
@property (nonatomic) BOOL isInEditingMode;

-(IBAction)didPressCancelButton:(UIBarButtonItem *)sender;
-(IBAction)didPressAddButton:(UIBarButtonItem *)sender;
-(IBAction)didSelectPriority:(UISegmentedControl *)sender;

@end

ACAddTaskViewController.m

#import "ACAddTaskViewController.h"
#import "ACSelectCategoryViewController.h"
#import "ACViewController.h"
#import "MGSwipeButton.h"
#import "UIColor+FlatColors.h"
#import "MBAutoGrowingTextView.h"
#import "ACReminder.h"


@interface ACAddTaskViewController () <UITextViewDelegate>

@property (strong, nonatomic) ACCategory *selectedCategory;

@property (strong, nonatomic) IBOutlet MBAutoGrowingTextView *taskTextField;
@property (strong, nonatomic) IBOutlet MBAutoGrowingTextView *taskDescriptionTextField;

@property (strong, nonatomic) NSDate *selectedDueDate;
@property (strong, nonatomic) NSString *dueDateString;

@property (strong, nonatomic) NSDate *selectedReminderDate;
@property (strong, nonatomic) NSString *reminderDateString;

@property (strong, nonatomic) UISegmentedControl *priorityPicker;
@property (nonatomic) int selectedPriority;

@property (nonatomic) int numberOfLines;

@property (nonatomic) BOOL taskDueDateIsSet;
@property (nonatomic) BOOL taskReminderIsSet;

@property (strong, nonatomic) NSMutableArray *cellNames;

@property (nonatomic) BOOL isDueDatePickerEnabled;
@property (nonatomic) BOOL isReminderDatePickerEnabled;

@property (strong, nonatomic) NSDateFormatter *dateFormatter;

@end


@implementation ACAddTaskViewController

-(NSDateFormatter *)dateFormatter
{
    if (!_dateFormatter)
    {
        _dateFormatter = [[NSDateFormatter alloc] init];
        _dateFormatter.dateStyle = NSDateFormatterMediumStyle;
    }
    return  _dateFormatter;
}

-(void)viewDidLoad
{
    [super viewDidLoad];

    self.taskTextField.delegate = self;

    self.tableView.dataSource = self;
    self.tableView.delegate = self;
    self.automaticallyAdjustsScrollViewInsets = NO; //Fix for textfield not starting from top
    self.tableView.contentInset = UIEdgeInsetsMake(-35, 0, 0, 0);
    self.tableView.estimatedRowHeight = 44;

    self.selectedCategory = self.categories[1];

    self.taskTextField.text = @"Task Name";
    self.taskDescriptionTextField.text = @"Task Details";
    self.taskTextField.textColor = [UIColor flatSilverColor];
    self.taskDescriptionTextField.textColor = [UIColor flatSilverColor];
    self.taskTextField.delegate = self;
    self.taskDescriptionTextField.delegate = self;



}


#pragma mark TableView Delegate

-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    if (section == 0)
    {
        return 1;
    }
    else if(section ==1)
    {
        [self.cellNames removeAllObjects];
        [self.cellNames addObjectsFromArray:@[@"dueDateCell", @"reminderCell"]];
        if (self.isDueDatePickerEnabled)
        {
            [self.cellNames removeAllObjects];
            [self.cellNames addObjectsFromArray:@[@"dueDateCell", @"datePickerCell", @"reminderCell"]];
            return 3;
        }
        else if (self.isReminderDatePickerEnabled)
        {
            [self.cellNames removeAllObjects];
            [self.cellNames addObjectsFromArray:@[@"dueDateCell", @"reminderCell", @"datePickerCell"]];
            return 3;
        }
        return 2;
    }
    else
    {
        return 1;
    }
}

-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return 3;
}

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (indexPath.row ==0 && indexPath.section == 0)
    {
        ACCategoryTableViewCell *categoryCell = [tableView dequeueReusableCellWithIdentifier:@"categoryCell"];
        self.selectedCategory.color = [UIColor greenColor];
        [categoryCell setupCellWithCategoryName:self.selectedCategory.name categoryImage:@"inboxIcon.png" categoryColor:self.selectedCategory.color];
        return categoryCell;
    }
    if (indexPath.section == 1)
    {
        NSString *currentCell = [self.cellNames objectAtIndex:indexPath.row];
        if ([currentCell isEqualToString:@"dueDateCell"])
        {

            ACDueDateTableViewCell *dueDateCell = [tableView dequeueReusableCellWithIdentifier:@"dueDateCell" forIndexPath:indexPath];

            //Check and display current date if dueDatePickerIsEnabled
            if (self.selectedDueDate == NO && self.isDueDatePickerEnabled == YES)
            {
                [dueDateCell setupCellWithDueDate:[self.dateFormatter stringFromDate:[NSDate date]] forEnabledState:self.taskDueDateIsSet];
            }
            else
            {
                [dueDateCell setupCellWithDueDate:self.dueDateString forEnabledState:self.taskDueDateIsSet];
            }
            dueDateCell.delegate = self;
            return dueDateCell;
        }
        else if ([currentCell isEqualToString:@"datePickerCell"])
        {
            ACDueDatePickerCell *datePickerCell = [tableView dequeueReusableCellWithIdentifier:@"datePickerCell" forIndexPath:indexPath];
            datePickerCell.datePicker.backgroundColor = [UIColor greenColor];
            if (self.isDueDatePickerEnabled)
            {
                datePickerCell.datePicker.datePickerMode = UIDatePickerModeDate;
                [datePickerCell.datePicker removeTarget:self action:@selector(reminderDateDidChange:) forControlEvents:UIControlEventValueChanged];
                [datePickerCell.datePicker addTarget:self action:@selector(dueDateDidChange:) forControlEvents:UIControlEventValueChanged];
                if (self.selectedDueDate == nil)
                {
                    datePickerCell.datePicker.date = [NSDate date];
                    self.selectedDueDate = [NSDate date];
                    self.dueDateString = [self.dateFormatter stringFromDate:self.selectedDueDate];

                }
            }
            else if (self.isReminderDatePickerEnabled)
            {
                datePickerCell.datePicker.datePickerMode = UIDatePickerModeDateAndTime;
                [datePickerCell.datePicker removeTarget:self action:@selector(dueDateDidChange:) forControlEvents:UIControlEventValueChanged];
                [datePickerCell.datePicker addTarget:self action:@selector(reminderDateDidChange:) forControlEvents:UIControlEventValueChanged];
                if (self.selectedReminderDate == nil)
                {
                    datePickerCell.datePicker.date = [NSDate date];
                    self.selectedReminderDate = [NSDate date];
                    self.reminderDateString = [[ACReminder dateFormat] stringFromDate:self.selectedDueDate];
                }
            }
            return datePickerCell;
        }
        else if([currentCell isEqualToString:@"reminderCell"])
        {
            ACReminderDateTableViewCell *reminderDateCell = [tableView dequeueReusableCellWithIdentifier:@"reminderCell" forIndexPath:indexPath];

            //Check and display current date if dueDatePickerIsEnabled
            if (self.selectedReminderDate == NO && self.isReminderDatePickerEnabled == YES)
            {
                [reminderDateCell setupCellWithReminderDate:[[ACReminder dateFormat] stringFromDate:[NSDate date]] forEnabledState:self.taskReminderIsSet];
            }
            else
            {
                [reminderDateCell setupCellWithReminderDate:self.reminderDateString forEnabledState:self.taskReminderIsSet];
            }
            reminderDateCell.delegate = self;
            return reminderDateCell;
        }
    }
    else if (indexPath.row==0 && indexPath.section == 2)
    {
        ACPriorityPickerTableViewCell *priorityCell = [tableView dequeueReusableCellWithIdentifier:@"priorityCell" forIndexPath:indexPath];
        [priorityCell setupCellWithPriority:self.selectedPriority];
        return priorityCell;
    }
        return nil;
}

-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (self.isDueDatePickerEnabled == YES && indexPath.section == 1 && indexPath.row == 1)
    {
        return 150;
    }
    else if (self.isReminderDatePickerEnabled == YES && indexPath.section == 1 && indexPath.row == 2)
    {
        return 150;
    }
    return 44;
}

-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (indexPath.row ==0 && indexPath.section ==0)
    {
        [self performSegueWithIdentifier:@"pushToAddCategory" sender:tableView];
    }
    if ([[[self.tableView cellForRowAtIndexPath:indexPath] reuseIdentifier] isEqualToString:@"dueDateCell"])
    {
        if (self.isDueDatePickerEnabled)
        {
            self.isDueDatePickerEnabled = NO;
            [self performSelector:@selector(didFinishEditingDueDate:) withObject:nil];
        }
        else if (!self.isDueDatePickerEnabled)
        {
            self.isDueDatePickerEnabled = YES;
            self.isReminderDatePickerEnabled = NO;
            self.taskDueDateIsSet = YES;

        }
        [self.tableView reloadData];
    }

    else if([[[self.tableView cellForRowAtIndexPath:indexPath] reuseIdentifier] isEqualToString:@"reminderCell"])
    {
        if (self.isReminderDatePickerEnabled)
        {
            self.isReminderDatePickerEnabled = NO;
            [self performSelector:@selector(didFinishEditingReminderTime:) withObject:nil];
        }
        else if (!self.isReminderDatePickerEnabled)
        {
            self.isReminderDatePickerEnabled = YES;
            self.isDueDatePickerEnabled = NO;
            self.taskReminderIsSet = YES;

        }
        [self.tableView reloadData];
    }

}


#pragma mark DatePicker Methods

-(void)dueDateDidChange:(UIDatePicker *)sender
{
    self.dueDateString = [self.dateFormatter stringFromDate:sender.date];
    self.selectedDueDate = sender.date;
    [self.tableView reloadRowsAtIndexPaths:[self arrayForIndexPath:0 section:1] withRowAnimation:UITableViewRowAnimationAutomatic];
}

-(IBAction)didFinishEditingDueDate:(UIBarButtonItem *)sender
{
    self.taskDueDateIsSet = YES;
    [self.tableView reloadData];
}

-(IBAction)didDeleteDueDate:(id)sender
{
    self.dueDateString = @"";
    self.taskDueDateIsSet = NO;
    self.selectedDueDate = nil;
    [self.tableView reloadRowsAtIndexPaths:[self arrayForIndexPath:0 section:1] withRowAnimation:UITableViewRowAnimationAutomatic];
}


#pragma mark TimePicker Methods

-(void)reminderDateDidChange:(UIDatePicker *)sender
{
    self.reminderDateString = [[ACReminder dateFormat] stringFromDate:sender.date];
    self.selectedReminderDate = sender.date;
    [self.tableView reloadRowsAtIndexPaths:[self arrayForIndexPath:1 section:1] withRowAnimation:UITableViewRowAnimationAutomatic];
}

-(IBAction)didFinishEditingReminderTime:(UIBarButtonItem *)sender
{
    self.taskReminderIsSet = YES;
    [self.tableView reloadData];
}

-(IBAction)didDeleteReminderTime:(UIBarButtonItem *)sender
{

    self.reminderDateString = @"";
    self.taskReminderIsSet = NO;
    self.selectedReminderDate = nil;
    [self.tableView reloadRowsAtIndexPaths:[self arrayForIndexPath:1 section:1] withRowAnimation:UITableViewRowAnimationAutomatic];
}


#pragma mark SwipeCell Methods

-(BOOL)swipeTableCell:(MGSwipeTableCell *)cell canSwipe:(MGSwipeDirection)direction
{
    if (self.isDueDatePickerEnabled)
    {
        return NO;
    }
    return YES;
}

-(NSArray *)swipeTableCell:(MGSwipeTableCell *)cell swipeButtonsForDirection:(MGSwipeDirection)direction
             swipeSettings:(MGSwipeSettings *)swipeSettings expansionSettings:(MGSwipeExpansionSettings *)expansionSettings
{
    swipeSettings.transition = MGSwipeTransitionClipCenter;
    swipeSettings.keepButtonsSwiped = YES;
    expansionSettings.buttonIndex = 0;
    expansionSettings.threshold = 1.5;
    expansionSettings.expansionLayout = MGSwipeExpansionLayoutCenter;
    expansionSettings.triggerAnimation.easingFunction = MGSwipeEasingFunctionCubicOut;
    expansionSettings.fillOnTrigger = NO;
    UIColor *redColor = [UIColor colorWithRed:1.0 green:(59 / 255.0) blue:(50 / 255.0) alpha:1.0];
    if (direction == MGSwipeDirectionRightToLeft)
    {
        MGSwipeButton *deleteButton = [MGSwipeButton buttonWithTitle:@"Delete" backgroundColor:redColor padding:15 callback:^BOOL (MGSwipeTableCell *sender)
        {
            if ([self.tableView indexPathForCell:cell].row == 0 && [self.tableView indexPathForCell:cell].section == 1)
            {
                [self performSelector:@selector(didDeleteDueDate:) withObject:nil];
            }
            if ([self.tableView indexPathForCell:cell].row == 1 && [self.tableView indexPathForCell:cell].section == 1)
            {
                [self performSelector:@selector(didDeleteReminderTime:) withObject:nil];
            }
            return YES;
        }];
        return @[deleteButton];
    }
    return nil;
}


#pragma mark Helper Methods

-(NSArray *)arrayForIndexPath:(int)row section:(int)section
{
    return [NSArray arrayWithObject:[NSIndexPath indexPathForRow:row inSection:section]];;
}

-(ACDueDate *)dueDate
{
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"date CONTAINS %@", self.dueDateString];
    NSArray *date = [self.dates filteredArrayUsingPredicate:predicate];
    if ([date count] == 1)
    {
        return date[0];
    }
    else
    {
        ACDueDate *dueDate = [ACDueDate insertDueDateWithDate:self.selectedDueDate];
        [self.dates addObject:dueDate];
        return dueDate;
    }
}


#pragma mark ACSelectCategoryViewControllerDelegate

-(void)selectedCategory:(ACCategory *)category categories:(NSArray *)categories
{
    [self dismissViewControllerAnimated:YES completion:nil];
    self.selectedCategory = category;
    [self.tableView reloadRowsAtIndexPaths:[self arrayForIndexPath:0 section:0] withRowAnimation:UITableViewRowAnimationAutomatic];
    if (([self.categories count] - 1) != [categories count])
    {
        [self.categories removeObjectsInRange:NSMakeRange(1, ([self.categories count] - 1))];
        [self.categories addObjectsFromArray:categories];
    }
}

-(void)didCancelSelectingCategory:(NSArray *)categories
{
    [self dismissViewControllerAnimated:YES completion:nil];
    if (([self.categories count] - 1) != [categories count])
    {
        [self.categories removeObjectsInRange:NSMakeRange(1, ([self.categories count] - 1))];
        [self.categories addObjectsFromArray:categories];
    }
}


#pragma mark - Navigation

-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if ([segue.identifier isEqualToString:@"pushToAddCategory"])
    {
        UINavigationController *navigationController = segue.destinationViewController;
        ACSelectCategoryViewController *selectCategoryViewController = (ACSelectCategoryViewController *)navigationController.topViewController;
        selectCategoryViewController.categories = [self.categories mutableCopy];
        [selectCategoryViewController.categories removeObjectAtIndex:0];
        selectCategoryViewController.delegate = self;
    }
}


#pragma mark UIBarButton Actions

-(IBAction)didPressCancelButton:(UIBarButtonItem *)sender
{
    [self.delegate taskAddingCancelled];
}

-(IBAction)didPressAddButton:(UIBarButtonItem *)sender
{
    self.task = [ACTask insertTaskWithName:self.taskTextField.text details:self.taskDescriptionTextField.text serial:nil priority:self.selectedPriority dueDate:[self dueDate] reminderDate:self.selectedReminderDate isCompleted:NO intoCategory:self.selectedCategory];
    [ACTask saveTasks];
    if (![self.taskTextField.text isEqualToString:@""])
    {
        [self.delegate taskAdded:self.task isEditing:self.isInEditingMode == FALSE categoriesList:self.categories dates:self.dates];
    }
    else
    {
        [self.delegate taskAddingCancelled];
    }
}


#pragma mark Switch and Segment Methods

-(IBAction)didSelectPriority:(UISegmentedControl *)sender
{
    self.selectedPriority = (int) sender.selectedSegmentIndex;
}


#pragma mark TextView Methods

-(BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
{
    if ([text isEqualToString:@"\n"])
    {
        [textView resignFirstResponder];
        return NO;
    }
    return YES;
}

-(BOOL)textViewShouldBeginEditing:(UITextView *)textView
{
    if (textView == self.taskTextField)
    {
        self.taskTextField.text = @"";
        self.taskTextField.textColor = [UIColor whiteColor];
    }
    else if (textView == self.taskDescriptionTextField)
    {
        self.taskDescriptionTextField.text = @"";
        self.taskDescriptionTextField.textColor = [UIColor whiteColor];
    }
    return YES;
}

-(BOOL)textViewShouldEndEditing:(UITextView *)textView
{
    if (self.taskTextField.text.length == 0)
    {
        self.taskTextField.textColor = [UIColor flatSilverColor];
        self.taskTextField.text = @"Task Name";
        [self.taskTextField resignFirstResponder];
    }
    else if (self.taskDescriptionTextField.text.length == 0)
    {
        self.taskDescriptionTextField.textColor = [UIColor flatSilverColor];
        self.taskDescriptionTextField.text = @"Task Details";
        [self.taskDescriptionTextField resignFirstResponder];
    }
    return YES;

}


#pragma Lazy Initialization

-(NSMutableArray *)cellNames
{
    if (!_cellNames) _cellNames = [[NSMutableArray alloc] init];
    return _cellNames;
}

@end
\$\endgroup\$
  • \$\begingroup\$ Added Code to main post \$\endgroup\$ – Shrijit Singh Feb 15 '16 at 8:49
  • 1
    \$\begingroup\$ The post borders on too broad. You're asking us to review two whole view controllers. You'd be better off splitting this into two questions--one for each view controller. \$\endgroup\$ – nhgrif Feb 15 '16 at 18:32
4
\$\begingroup\$

There are a lot of things wrong here that I'm probably not mentioning at all. There is so much code that I instead want to focus on the big picture problems and the repeated problems (that you need to correct now and keep correct in the future as you write more code).


weak vs strong

IBOutlet properties should not be declared as strong unless you have good reason to do so. In fact, the same is true for all properties, but outside of IBOutlet properties and delegates, you usually have a decent reason to declare something as strong. But for IBOutlet properties, it is rare that you have a good reason to declare them as strong. The only time you ever need to even consider doing this is if the UI element you're referencing ever spends any amount of time without a superview.


Private vs Public

IBOutlet properties as well as IBAction method declarations never have any business belonging in the .h file of a view controller. Your view controller is in charge of controlling its views. No other class should ever be directly manipulating a view controller's view. And much like we should declare things weak unless we have a compelling reason to mark them as strong, we should default to omitting things from the public part of our class (the .h header file) unless we have a compelling reason to include them there. And when it comes to IBOutlets which should only be referenced by and modified directly by the view controller itself and IBAction methods which should only ever be called by the UIControl object that is hooked up to that action, these things should always be considered private to the view controller.


Single Responsibility Principle

Your classes are doing way too much. You have no real data model for your project, and that's resulting in part of this 'doing too much'. Your utility classes are missing and/or incomplete. You're importing a UIColor extension, but also manually creating other colors in this file--why don't you just put all of the colors you're going to use in extensions and call those extension methods (just as one example of how these classes could do less). But simply implementing actual data models properly will reduce a lot of this code.

\$\endgroup\$
  • \$\begingroup\$ Thanks so much for the valuable feedback. I just didn't get the last point. I understand there is a lot of code in the view controllers, so i will be moving them to the appropriate classes where required. I did not understand what you meant by the model layer could you maybe give an example cause i did make model for all the tasks, categories, etc. \$\endgroup\$ – Shrijit Singh Feb 15 '16 at 19:06
2
\$\begingroup\$

I have a few suggestions:

  1. Do you need this outside the scope of class?

    @property (strong, nonatomic) IBOutlet UIButton *selectCategoryButton;
    @property (strong, nonatomic) IBOutlet UIButton *addNewButton;
    @property (strong, nonatomic) IBOutlet UITextField *addTaskTextField;
    @property (strong, nonatomic) IBOutlet UIScrollView *menuBarScrollView;
    

    If not, then why cannot we set as _weak IBOutlet reference objects.

  2. This implementation part should not be in VC. but in model class:

    -(void)performFirstRunSetup {
        NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
    

    These initializations and value setups should be in model/business layer and not in VC.

    if (![userDefaults valueForKey:@"isFirstRun"])
    {
        ACCategory *overview = [ACCategory insertCategoryWithName:@"Overview" color:nil serial:0];
        ACCategory *inbox = [ACCategory insertCategoryWithName:@"Inbox" color:nil serial:1];
        ACCategory *home = [ACCategory insertCategoryWithName:@"Home" color:nil serial:2];
        ACCategory *work = [ACCategory insertCategoryWithName:@"Work" color:nil serial:3];
        ACCategory *shopping = [ACCategory insertCategoryWithName:@"Shopping" color:nil serial:4];
        [ACCategory saveCategories];
        [userDefaults setBool:YES forKey:@"isFirstRun"];
    }
    
  3. For tableview datasource, please look into "Light View Controllers"

\$\endgroup\$
  • \$\begingroup\$ Thanks for your suggestions. I didn't quite get what you meant by this one first suggestion. Did you mean i should set IBOutlets as weak instead of strong? \$\endgroup\$ – Shrijit Singh Feb 15 '16 at 11:35
  • \$\begingroup\$ purpose of using @property, instead of variable. @property/@synthesize is used for getter/setter. which is not required here. So you can access them as variables. \$\endgroup\$ – Ankit Thakur Feb 15 '16 at 12:43
  • \$\begingroup\$ So when should i use @property and when should i use a variable? \$\endgroup\$ – Shrijit Singh Feb 15 '16 at 13:09
  • \$\begingroup\$ use @property, if the object needs to be accessed as getter/setters. As with reference to Abstraction in OOPs, we should properly declare private, protected and public variables, but we are normally using getter/setter funtions. property is needed, if you want to specifically handle the variable type like readonly, assign, copy; else use them normally like variables. \$\endgroup\$ – Ankit Thakur Feb 16 '16 at 4:32

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.