Skip to content

Commit

Permalink
Can now go back and forward through views, properties observed for ab…
Browse files Browse the repository at this point in the history
…ility to forward and backwards.
  • Loading branch information
danpalmer committed Oct 5, 2012
1 parent 2016e14 commit f1f7a87
Show file tree
Hide file tree
Showing 13 changed files with 1,172 additions and 43 deletions.
7 changes: 3 additions & 4 deletions DPSetupWindow.h
Expand Up @@ -11,11 +11,10 @@
@protocol DPSetupWindowStageViewController <NSObject>

/*
Stage view controllers must implement these as they will determine whether each of the buttons can be clicked.
Stage view controllers must implement these as they will be observed by the setup window to determine whether the interface buttons should be enabled.
*/
- (BOOL)canContinue;
- (BOOL)canGoBack;
- (BOOL)canCancel;
@property (readonly) BOOL canContinue;
@property (readonly) BOOL canGoBack;

@optional

Expand Down
207 changes: 169 additions & 38 deletions DPSetupWindow.m
Expand Up @@ -10,8 +10,15 @@

@interface DPSetupWindow ()

@property (assign) IBOutlet NSImageView *backgroundImageView;
@property (retain) NSImageView *imageView;
@property (retain) NSBox *contentBox;
@property (retain) NSButton *cancelButton;
@property (retain) NSButton *backButton;
@property (retain) NSButton *nextButton;

@property (copy) void(^completionHandler)(BOOL);
@property (retain) NSArray *viewControllers;
@property (assign) NSViewController *currentViewController;

@end

Expand All @@ -24,77 +31,201 @@ - (id)initWithViewControllers:(NSArray *)viewControllers completionHandler:(void
self = [super initWithContentRect:contentRect styleMask:(NSTitledWindowMask|NSClosableWindowMask) backing:NSBackingStoreBuffered defer:YES];
if (self == nil) return nil;

currentStage = 0;
if (!viewControllers || [viewControllers count] == 0) return nil;
if (!completionHandler) return nil;

currentStage = -1;
_animates = YES;
[self setViewControllers:viewControllers];
[self setCompletionHandler:completionHandler];

[self setContentView:[self initialiseContentViewForRect:contentRect]];
[self next:nil];
return self;
}

- (NSView *)initialiseContentViewForRect:(NSRect)contentRect {
NSView *contentView = [[NSView alloc] initWithFrame:contentRect];

NSButton *cancelButton = [[NSButton alloc] initWithFrame:NSMakeRect(145, 13, 97, 32)];
[cancelButton setBezelStyle:NSRoundedBezelStyle];
[cancelButton setTarget:self];
[cancelButton setAction:@selector(cancel:)];
[cancelButton setTitle:@"Cancel"];
[contentView addSubview:cancelButton];

NSButton *backButton = [[NSButton alloc] initWithFrame:NSMakeRect(372, 13, 97, 32)];
[backButton setBezelStyle:NSRoundedBezelStyle];
[backButton setTarget:self];
[backButton setAction:@selector(back:)];
[backButton setTitle:@"Back"];
[contentView addSubview:backButton];

NSButton *nextButton = [[NSButton alloc] initWithFrame:NSMakeRect(469, 13, 97, 32)];
[nextButton setBezelStyle:NSRoundedBezelStyle];
[nextButton setTarget:self];
[nextButton setAction:@selector(next:)];
[nextButton setTitle:@"Continue"];
[contentView addSubview:nextButton];

NSImageView *imageView = [[NSImageView alloc] initWithFrame:NSMakeRect(-40, 60, 320, 320)];
[imageView setAlphaValue:0.3];
[imageView setImageScaling:NSImageScaleProportionallyUpOrDown];
[imageView setImage:[[NSApplication sharedApplication] applicationIconImage]];
[contentView addSubview:imageView];

NSBox *box = [[NSBox alloc] initWithFrame:NSMakeRect(148, 57, 415, 345)];
[box setTitlePosition:(NSNoTitle)];
[contentView addSubview:box];
_cancelButton = [[NSButton alloc] initWithFrame:NSMakeRect(145, 13, 97, 32)];
[_cancelButton setBezelStyle:NSRoundedBezelStyle];
[_cancelButton setTarget:self];
[_cancelButton setAction:@selector(cancel:)];
[_cancelButton setTitle:@"Cancel"];
[contentView addSubview:_cancelButton];

_backButton = [[NSButton alloc] initWithFrame:NSMakeRect(372, 13, 97, 32)];
[_backButton setBezelStyle:NSRoundedBezelStyle];
[_backButton setTarget:self];
[_backButton setAction:@selector(back:)];
[_backButton setTitle:@"Back"];
[contentView addSubview:_backButton];

_nextButton = [[NSButton alloc] initWithFrame:NSMakeRect(469, 13, 97, 32)];
[_nextButton setBezelStyle:NSRoundedBezelStyle];
[_nextButton setTarget:self];
[_nextButton setAction:@selector(next:)];
[_nextButton setTitle:@"Continue"];
[contentView addSubview:_nextButton];

_imageView = [[NSImageView alloc] initWithFrame:NSMakeRect(-40, 60, 320, 320)];
[_imageView setAlphaValue:0.3];
[_imageView setImageScaling:NSImageScaleProportionallyUpOrDown];
[_imageView setImage:[[NSApplication sharedApplication] applicationIconImage]];
[contentView addSubview:_imageView];

_contentBox = [[NSBox alloc] initWithFrame:NSMakeRect(148, 57, 415, 345)];
[_contentBox setTitlePosition:(NSNoTitle)];
[contentView addSubview:_contentBox];

return contentView;
}

#pragma mark -
#pragma mark Customisation

- (void)setBackgroundImage:(NSImage *)backgroundImage {
_backgroundImage = backgroundImage;
[[self backgroundImageView] setImage:backgroundImage];
[[self imageView] setImage:backgroundImage];
}

- (NSImage *)backgroundImage {
return _backgroundImage;
}

#pragma mark -
#pragma mark Flow Control

- (void)next:(id)sender {

if (currentStage == [[self viewControllers] count]) {
return; // we have already triggered the callback
}

NSViewController<DPSetupWindowStageViewController> *previousViewController = nil;
if (currentStage >= 0 && currentStage < [[self viewControllers] count]) {
previousViewController = [[self viewControllers] objectAtIndex:currentStage];
}

currentStage++;
if (currentStage == [[self viewControllers] count]) {
[self completionHandler](YES);
return;
}

NSViewController<DPSetupWindowStageViewController> *nextViewController = [[self viewControllers] objectAtIndex:currentStage];
NSView *view = [nextViewController view];

if ([self animates] && previousViewController) {
[NSAnimationContext beginGrouping];

if ([[NSApp currentEvent] modifierFlags] & NSShiftKeyMask) {
[[NSAnimationContext currentContext] setDuration:3.0];
}
[[NSAnimationContext currentContext] setCompletionHandler:^{
[[previousViewController view] removeFromSuperviewWithoutNeedingDisplay];
}];

[view setFrame:NSMakeRect(400, 0, 400, 330)];
[[self contentBox] addSubview:view];
[[view animator] setFrameOrigin:NSMakePoint(0, 0)];
[[[previousViewController view] animator] setFrameOrigin:NSMakePoint(-400, 0)];

[NSAnimationContext endGrouping];
} else {
[view setFrame:NSMakeRect(0, 0, 400, 330)];
if (previousViewController) {
[[previousViewController view] removeFromSuperviewWithoutNeedingDisplay];
}
[[self contentBox] addSubview:view];
}

[self registerObserversForPreviousViewController:previousViewController nextViewController:nextViewController];
[self recalculateButtonEnabledStates];
}

- (void)back:(id)sender {

if (currentStage == 0) {
return;
}

NSViewController<DPSetupWindowStageViewController> *previousViewController = nil;
if (currentStage >= 0 && currentStage < [[self viewControllers] count]) {
previousViewController = [[self viewControllers] objectAtIndex:currentStage];
}

currentStage--;
if (currentStage == [[self viewControllers] count]) {
[self completionHandler](YES);
return;
}

NSViewController<DPSetupWindowStageViewController> *nextViewController = [[self viewControllers] objectAtIndex:currentStage];
NSView *view = [nextViewController view];

if ([self animates] && previousViewController) {
[NSAnimationContext beginGrouping];

if ([[NSApp currentEvent] modifierFlags] & NSShiftKeyMask) {
[[NSAnimationContext currentContext] setDuration:2.0];
}
[[NSAnimationContext currentContext] setCompletionHandler:^{
[[previousViewController view] removeFromSuperviewWithoutNeedingDisplay];
}];

[view setFrame:NSMakeRect(-400, 0, 400, 330)];
[[self contentBox] addSubview:view];
[[view animator] setFrameOrigin:NSMakePoint(0, 0)];
[[[previousViewController view] animator] setFrameOrigin:NSMakePoint(400, 0)];

[NSAnimationContext endGrouping];
} else {
[view setFrame:NSMakeRect(0, 0, 400, 330)];
if (previousViewController) {
[[previousViewController view] removeFromSuperviewWithoutNeedingDisplay];
}
[[self contentBox] addSubview:view];
}

[self registerObserversForPreviousViewController:previousViewController nextViewController:nextViewController];
[self recalculateButtonEnabledStates];
}

- (void)cancelOperation:(id)sender {
[self cancel:sender];
}

#pragma mark -
#pragma mark Flow Control

- (void)cancel:(id)sender {
[[NSApplication sharedApplication] endSheet:self returnCode:0];
[self completionHandler](NO);
}

- (void)back:(id)sender {
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
[self recalculateButtonEnabledStates];
}

- (void)recalculateButtonEnabledStates {
NSViewController<DPSetupWindowStageViewController> *currentViewController = [[self viewControllers] objectAtIndex:currentStage];
if ([currentViewController respondsToSelector:@selector(canContinue)]) {
[[self nextButton] setEnabled:[currentViewController canContinue]];
}

if ([currentViewController respondsToSelector:@selector(canGoBack)]) {
[[self backButton] setEnabled:[currentViewController canGoBack]];
}

if (currentStage == 0) {
[[self backButton] setEnabled:NO];
}
}

- (void)next:(id)sender {
- (void)registerObserversForPreviousViewController:(NSViewController *)previousViewController nextViewController:(NSViewController *)nextViewController {

[previousViewController removeObserver:self forKeyPath:@"canContinue"];
[previousViewController removeObserver:self forKeyPath:@"canGoBack"];
[nextViewController addObserver:self forKeyPath:@"canContinue" options:(NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld) context:NULL];
[nextViewController addObserver:self forKeyPath:@"canGoBack" options:(NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld) context:NULL];
}

@end
38 changes: 38 additions & 0 deletions DPSetupWindow.xcodeproj/project.pbxproj
Expand Up @@ -14,6 +14,12 @@
379CC9D2161F416E009FEB86 /* DPAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 379CC9D1161F416E009FEB86 /* DPAppDelegate.m */; };
379CC9D5161F416E009FEB86 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 379CC9D3161F416E009FEB86 /* MainMenu.xib */; };
379CC9DE161F4F7A009FEB86 /* DPSetupWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = 379CC9DC161F4F7A009FEB86 /* DPSetupWindow.m */; };
379CC9E9161F7E62009FEB86 /* DPFirstViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 379CC9E7161F7E62009FEB86 /* DPFirstViewController.m */; };
379CC9EA161F7E62009FEB86 /* DPFirstViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 379CC9E8161F7E62009FEB86 /* DPFirstViewController.xib */; };
379CC9EF161F7E7F009FEB86 /* DPSecondViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 379CC9ED161F7E7F009FEB86 /* DPSecondViewController.m */; };
379CC9F0161F7E7F009FEB86 /* DPSecondViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 379CC9EE161F7E7F009FEB86 /* DPSecondViewController.xib */; };
379CC9F4161F7E8A009FEB86 /* DPThirdViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 379CC9F2161F7E8A009FEB86 /* DPThirdViewController.m */; };
379CC9F5161F7E8A009FEB86 /* DPThirdViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 379CC9F3161F7E8A009FEB86 /* DPThirdViewController.xib */; };
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
Expand All @@ -32,6 +38,15 @@
379CC9D4161F416E009FEB86 /* en */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = en; path = en.lproj/MainMenu.xib; sourceTree = "<group>"; };
379CC9DB161F4F7A009FEB86 /* DPSetupWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DPSetupWindow.h; sourceTree = SOURCE_ROOT; };
379CC9DC161F4F7A009FEB86 /* DPSetupWindow.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DPSetupWindow.m; sourceTree = SOURCE_ROOT; };
379CC9E6161F7E62009FEB86 /* DPFirstViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DPFirstViewController.h; path = ViewControllers/DPFirstViewController.h; sourceTree = "<group>"; };
379CC9E7161F7E62009FEB86 /* DPFirstViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = DPFirstViewController.m; path = ViewControllers/DPFirstViewController.m; sourceTree = "<group>"; };
379CC9E8161F7E62009FEB86 /* DPFirstViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = DPFirstViewController.xib; path = ViewControllers/DPFirstViewController.xib; sourceTree = "<group>"; };
379CC9EC161F7E7F009FEB86 /* DPSecondViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DPSecondViewController.h; path = ViewControllers/DPSecondViewController.h; sourceTree = "<group>"; };
379CC9ED161F7E7F009FEB86 /* DPSecondViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = DPSecondViewController.m; path = ViewControllers/DPSecondViewController.m; sourceTree = "<group>"; };
379CC9EE161F7E7F009FEB86 /* DPSecondViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = DPSecondViewController.xib; path = ViewControllers/DPSecondViewController.xib; sourceTree = "<group>"; };
379CC9F1161F7E8A009FEB86 /* DPThirdViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DPThirdViewController.h; path = ViewControllers/DPThirdViewController.h; sourceTree = "<group>"; };
379CC9F2161F7E8A009FEB86 /* DPThirdViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = DPThirdViewController.m; path = ViewControllers/DPThirdViewController.m; sourceTree = "<group>"; };
379CC9F3161F7E8A009FEB86 /* DPThirdViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = DPThirdViewController.xib; path = ViewControllers/DPThirdViewController.xib; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -88,6 +103,7 @@
379CC9D0161F416E009FEB86 /* DPAppDelegate.h */,
379CC9D1161F416E009FEB86 /* DPAppDelegate.m */,
379CC9E0161F4F85009FEB86 /* SetupWindow */,
379CC9EB161F7E69009FEB86 /* ViewControllers */,
379CC9D3161F416E009FEB86 /* MainMenu.xib */,
379CC9C5161F416E009FEB86 /* Supporting Files */,
);
Expand Down Expand Up @@ -115,6 +131,22 @@
name = SetupWindow;
sourceTree = "<group>";
};
379CC9EB161F7E69009FEB86 /* ViewControllers */ = {
isa = PBXGroup;
children = (
379CC9E6161F7E62009FEB86 /* DPFirstViewController.h */,
379CC9E7161F7E62009FEB86 /* DPFirstViewController.m */,
379CC9E8161F7E62009FEB86 /* DPFirstViewController.xib */,
379CC9EC161F7E7F009FEB86 /* DPSecondViewController.h */,
379CC9ED161F7E7F009FEB86 /* DPSecondViewController.m */,
379CC9EE161F7E7F009FEB86 /* DPSecondViewController.xib */,
379CC9F1161F7E8A009FEB86 /* DPThirdViewController.h */,
379CC9F2161F7E8A009FEB86 /* DPThirdViewController.m */,
379CC9F3161F7E8A009FEB86 /* DPThirdViewController.xib */,
);
name = ViewControllers;
sourceTree = "<group>";
};
/* End PBXGroup section */

/* Begin PBXNativeTarget section */
Expand Down Expand Up @@ -170,6 +202,9 @@
379CC9C9161F416E009FEB86 /* InfoPlist.strings in Resources */,
379CC9CF161F416E009FEB86 /* Credits.rtf in Resources */,
379CC9D5161F416E009FEB86 /* MainMenu.xib in Resources */,
379CC9EA161F7E62009FEB86 /* DPFirstViewController.xib in Resources */,
379CC9F0161F7E7F009FEB86 /* DPSecondViewController.xib in Resources */,
379CC9F5161F7E8A009FEB86 /* DPThirdViewController.xib in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand All @@ -183,6 +218,9 @@
379CC9CB161F416E009FEB86 /* main.m in Sources */,
379CC9D2161F416E009FEB86 /* DPAppDelegate.m in Sources */,
379CC9DE161F4F7A009FEB86 /* DPSetupWindow.m in Sources */,
379CC9E9161F7E62009FEB86 /* DPFirstViewController.m in Sources */,
379CC9EF161F7E7F009FEB86 /* DPSecondViewController.m in Sources */,
379CC9F4161F7E8A009FEB86 /* DPThirdViewController.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
15 changes: 14 additions & 1 deletion DPSetupWindow/DPAppDelegate.m
Expand Up @@ -10,6 +10,10 @@

#import "DPSetupWindow.h"

#import "DPFirstViewController.h"
#import "DPSecondViewController.h"
#import "DPThirdViewController.h"

@interface DPAppDelegate ()

@property (retain) DPSetupWindow *setupFlow;
Expand All @@ -19,7 +23,16 @@ @interface DPAppDelegate ()
@implementation DPAppDelegate

- (void)applicationDidFinishLaunching:(NSNotification *)notification {
DPSetupWindow *setupFlow = [[DPSetupWindow alloc] initWithViewControllers:@[] completionHandler:^(BOOL completed) {

NSViewController *firstViewController = [[DPFirstViewController alloc] initWithNibName:@"DPFirstViewController" bundle:[NSBundle mainBundle]];
NSViewController *secondViewController = [[DPSecondViewController alloc] initWithNibName:@"DPSecondViewController" bundle:[NSBundle mainBundle]];
NSViewController *thirdViewController = [[DPThirdViewController alloc] initWithNibName:@"DPThirdViewController" bundle:[NSBundle mainBundle]];

DPSetupWindow *setupFlow = [[DPSetupWindow alloc] initWithViewControllers:@[
firstViewController,
secondViewController,
thirdViewController
] completionHandler:^(BOOL completed) {
if (!completed) {
NSLog(@"Cancelled setup process");
} else {
Expand Down
15 changes: 15 additions & 0 deletions DPSetupWindow/ViewControllers/DPFirstViewController.h
@@ -0,0 +1,15 @@
//
// DPFirstViewController.h
// DPSetupWindow
//
// Created by Dan Palmer on 05/10/2012.
// Copyright (c) 2012 Dan Palmer. All rights reserved.
//

#import <Cocoa/Cocoa.h>

#import "DPSetupWindow.h"

@interface DPFirstViewController : NSViewController <DPSetupWindowStageViewController>

@end

0 comments on commit f1f7a87

Please sign in to comment.