转自Di Wu's blog,原文:Architecting iOS Apps with VIPER
- - (void)findUpcomingItems
- {
- __weak typeof(self) welf = self;
- NSDate* today = [self.clock today];
- NSDate* endOfNextWeek = [[NSCalendar currentCalendar] dateForEndOfFollowingWeekWithDate:today];
- [self.dataManager todoItemsBetweenStartDate:today endDate:endOfNextWeek completionBlock:^(NSArray* todoItems) {
- [welf.output foundUpcomingItems:[welf upcomingItemsFromToDoItems:todoItems]];
- }];
- }
- @interface VTDTodoItem : NSObject
- @property (nonatomic, strong) NSDate* dueDate;
- @property (nonatomic, copy) NSString* name;
- + (instancetype)todoItemWithDueDate:(NSDate*)dueDate name:(NSString*)name;
- @end
- - (void)addNewEntry
- {
- [self.listWireframe presentAddInterface];
- }
- - (void)foundUpcomingItems:(NSArray*)upcomingItems
- {
- if ([upcomingItems count] == 0)
- {
- [self.userInterface showNoContentMessage];
- }
- else
- {
- [self updateUserInterfaceWithUpcomingItems:upcomingItems];
- }
- }
- @protocol VTDAddViewInterface
- - (void)setEntryName:(NSString *)name;
- - (void)setEntryDueDate:(NSDate *)date;
- @end
- @protocol VTDAddModuleInterface
- - (void)cancelAddAction;
- - (void)saveAddActionWithName:(NSString *)name dueDate:(NSDate *)dueDate
- @end
- @implementation VTDAddWireframe
- - (void)presentAddInterfaceFromViewController:(UIViewController *)viewController
- {
- VTDAddViewController *addViewController = [self addViewController];
- addViewController.eventHandler = self.addPresenter;
- addViewController.modalPresentationStyle = UIModalPresentationCustom;
- addViewController.transitioningDelegate = self;
- [viewController presentViewController:addViewController animated:YES completion:nil];
- self.presentedViewController = viewController;
- }
- #pragma mark - UIViewControllerTransitioningDelegate Methods
- - (id)animationControllerForDismissedController:(UIViewController *)dismissed
- {
- return [[VTDAddDismissalTransition alloc] init];
- }
- - (id)animationControllerForPresentedController:(UIViewController *)presented
- presentingController:(UIViewController *)presenting
- sourceController:(UIViewController *)source
- {
- return [[VTDAddPresentationTransition alloc] init];
- }
- @end
- @implementation VTDAddViewController
- - (void)viewDidAppear:(BOOL)animated
- {
- [super viewDidAppear:animated];
- UITapGestureRecognizer *gestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self
- action:@selector(dismiss)];
- [self.transitioningBackgroundView addGestureRecognizer:gestureRecognizer];
- self.transitioningBackgroundView.userInteractionEnabled = YES;
- }
- - (void)dismiss
- {
- [self.eventHandler cancelAddAction];
- }
- - (void)setEntryName:(NSString *)name
- {
- self.nameTextField.text = name;
- }
- - (void)setEntryDueDate:(NSDate *)date
- {
- [self.datePicker setDate:date];
- }
- - (IBAction)save:(id)sender
- {
- [self.eventHandler saveAddActionWithName:self.nameTextField.text
- dueDate:self.datePicker.date];
- }
- - (IBAction)cancel:(id)sender
- {
- [self.eventHandler cancelAddAction];
- }
- #pragma mark - UITextFieldDelegate Methods
- - (BOOL)textFieldShouldReturn:(UITextField *)textField
- {
- [textField resignFirstResponder];
- return YES;
- }
- @end
- @interface VTDListDataManager : NSObject
- @property (nonatomic, strong) VTDCoreDataStore *dataStore;
- - (void)todoItemsBetweenStartDate:(NSDate *)startDate endDate:(NSDate *)endDate completionBlock:(void (^)(NSArray *todoItems))completionBlock;
- @end
- @implementation VTDListDataManager
- - (void)todoItemsBetweenStartDate:(NSDate *)startDate endDate:(NSDate*)endDate completionBlock:(void (^)(NSArray *todoItems))completionBlock
- {
- NSCalendar *calendar = [NSCalendar autoupdatingCurrentCalendar];
- NSPredicate *predicate = [NSPredicate predicateWithFormat:@"(date >= %@) AND (date <= %@)", [calendar dateForBeginningOfDay:startDate], [calendar dateForEndOfDay:endDate]];
- NSArray *sortDescriptors = @[];
- __weak typeof(self) welf = self;
- [self.dataStore
- fetchEntriesWithPredicate:predicate
- sortDescriptors:sortDescriptors
- completionBlock:^(NSArray* entries) {
- if (completionBlock)
- {
- completionBlock([welf todoItemsFromDataStoreEntries:entries]);
- }
- }];
- }
- - (NSArray*)todoItemsFromDataStoreEntries:(NSArray *)entries
- {
- return [entries arrayFromObjectsCollectedWithBlock:^id(VTDManagedTodoItem *todo) {
- return [VTDTodoItem todoItemWithDueDate:todo.date name:todo.name];
- }];
- }
- @end
- static NSString *ListViewControllerIdentifier = @"VTDListViewController";
- @implementation VTDListWireframe
- - (void)presentListInterfaceFromWindow:(UIWindow *)window
- {
- VTDListViewController *listViewController = [self listViewControllerFromStoryboard];
- listViewController.eventHandler = self.listPresenter;
- self.listPresenter.userInterface = listViewController;
- self.listViewController = listViewController;
- [self.rootWireframe showRootViewController:listViewController
- inWindow:window];
- }
- - (VTDListViewController *)listViewControllerFromStoryboard
- {
- UIStoryboard *storyboard = [self mainStoryboard];
- VTDListViewController *viewController = [storyboard instantiateViewControllerWithIdentifier:ListViewControllerIdentifier];
- return viewController;
- }
- - (UIStoryboard *)mainStoryboard
- {
- UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main"
- bundle:[NSBundle mainBundle]];
- return storyboard;
- }
- @end
- @protocol VTDAddModuleInterface
- - (void)cancelAddAction;
- - (void)saveAddActionWithName:(NSString *)name dueDate:(NSDate *)dueDate;
- @end
- @protocol VTDAddModuleDelegate
- - (void)addModuleDidCancelAddAction;
- - (void)addModuleDidSaveAddAction;
- @end
- - (void)testFindingUpcomingItemsRequestsAllToDoItemsFromTodayThroughEndOfNextWeek
- {
- [[self.dataManager expect] todoItemsBetweenStartDate:self.today endDate:self.endOfNextWeek completionBlock:OCMOCK_ANY];
- [self.interactor findUpcomingItems];
- }
- - (void)testFindingUpcomingItemsWithOneItemDueTodayReturnsOneUpcomingItemsForToday
- {
- NSArray *todoItems = @[[VTDTodoItem todoItemWithDueDate:self.today name:@"Item 1"]];
- [self dataStoreWillReturnToDoItems:todoItems];
- NSArray *upcomingItems = @[[VTDUpcomingItem upcomingItemWithDateRelation:VTDNearTermDateRelationToday dueDate:self.today title:@"Item 1"]];
- [self expectUpcomingItems:upcomingItems];
- [self.interactor findUpcomingItems];
- }
- - (void)testFoundZeroUpcomingItemsDisplaysNoContentMessage
- {
- [[self.ui expect] showNoContentMessage];
- [self.presenter foundUpcomingItems:@[]];
- }
- - (void)testFoundUpcomingItemForTodayDisplaysUpcomingDataWithNoDay
- {
- VTDUpcomingDisplayData *displayData = [self displayDataWithSectionName:@"Today"
- sectionImageName:@"check"
- itemTitle:@"Get a haircut"
- itemDueDay:@""];
- [[self.ui expect] showUpcomingDisplayData:displayData];
- NSCalendar *calendar = [NSCalendar gregorianCalendar];
- NSDate *dueDate = [calendar dateWithYear:2014 month:5 day:29];
- VTDUpcomingItem *haircut = [VTDUpcomingItem upcomingItemWithDateRelation:VTDNearTermDateRelationToday dueDate:dueDate title:@"Get a haircut"];
- [self.presenter foundUpcomingItems:@[haircut]];
- }
- - (void)testFoundUpcomingItemForTomorrowDisplaysUpcomingDataWithDay
- {
- VTDUpcomingDisplayData *displayData = [self displayDataWithSectionName:@"Tomorrow"
- sectionImageName:@"alarm"
- itemTitle:@"Buy groceries"
- itemDueDay:@"Thursday"];
- [[self.ui expect] showUpcomingDisplayData:displayData];
- NSCalendar *calendar = [NSCalendar gregorianCalendar];
- NSDate *dueDate = [calendar dateWithYear:2014 month:5 day:29];
- VTDUpcomingItem *groceries = [VTDUpcomingItem upcomingItemWithDateRelation:VTDNearTermDateRelationTomorrow dueDate:dueDate title:@"Buy groceries"];
- [self.presenter foundUpcomingItems:@[groceries]];
- }
- - (void)testAddNewToDoItemActionPresentsAddToDoUI
- {
- [[self.wireframe expect] presentAddInterface];
- [self.presenter addNewEntry];
- }
- - (void)testShowingNoContentMessageShowsNoContentView
- {
- [self.view showNoContentMessage];
- XCTAssertEqualObjects(self.view.view, self.view.noContentView, @"the no content view should be the view");
- }
- - (void)testShowingUpcomingItemsShowsTableView
- {
- [self.view showUpcomingDisplayData:nil];
- XCTAssertEqualObjects(self.view.view, self.view.tableView, @"the table view should be the view");
- }
- struct UpcomingDisplayItem : Equatable, Printable {
- let title : String = ""
- let dueDate : String = ""
- var description : String { get {
- return "\(title) -- \(dueDate)"
- }}
- init(title: String, dueDate: String) {
- self.title = title
- self.dueDate = dueDate
- }
- }
- func == (leftSide: UpcomingDisplayItem, rightSide: UpcomingDisplayItem) -> Bool {
- var hasEqualSections = false
- hasEqualSections = rightSide.title == leftSide.title
- if hasEqualSections == false {
- return false
- }
- hasEqualSections = rightSide.dueDate == rightSide.dueDate
- return hasEqualSections
- }