master
1//
2// BlockAlertView.m
3//
4//
5
6#import "BlockAlertView.h"
7#import "BlockBackground.h"
8#import "BlockUI.h"
9
10@implementation BlockAlertView
11
12@synthesize view = _view;
13@synthesize backgroundImage = _backgroundImage;
14@synthesize vignetteBackground = _vignetteBackground;
15
16static UIImage *background = nil;
17static UIImage *backgroundlandscape = nil;
18static UIFont *titleFont = nil;
19static UIFont *messageFont = nil;
20static UIFont *buttonFont = nil;
21
22
23#pragma mark - init
24
25+ (void)initialize
26{
27 if (self == [BlockAlertView class])
28 {
29 background = [UIImage imageNamed:kAlertViewBackground];
30 background = [[background stretchableImageWithLeftCapWidth:0 topCapHeight:kAlertViewBackgroundCapHeight] retain];
31
32 backgroundlandscape = [UIImage imageNamed:kAlertViewBackgroundLandscape];
33 backgroundlandscape = [[backgroundlandscape stretchableImageWithLeftCapWidth:0 topCapHeight:kAlertViewBackgroundCapHeight] retain];
34
35 titleFont = [kAlertViewTitleFont retain];
36 messageFont = [kAlertViewMessageFont retain];
37 buttonFont = [kAlertViewButtonFont retain];
38 }
39}
40
41+ (BlockAlertView *)alertWithTitle:(NSString *)title message:(NSString *)message
42{
43 return [[[BlockAlertView alloc] initWithTitle:title message:message] autorelease];
44}
45
46+ (void)showInfoAlertWithTitle:(NSString *)title message:(NSString *)message
47{
48 BlockAlertView *alert = [[BlockAlertView alloc] initWithTitle:title message:message];
49 [alert setCancelButtonWithTitle:NSLocalizedString(@"Dismiss", nil) block:nil];
50 [alert show];
51 [alert release];
52}
53
54+ (void)showErrorAlert:(NSError *)error
55{
56 BlockAlertView *alert = [[BlockAlertView alloc] initWithTitle:NSLocalizedString(@"Operation Failed", nil) message:[NSString stringWithFormat:NSLocalizedString(@"The operation did not complete successfully: %@", nil), error]];
57 [alert setCancelButtonWithTitle:@"Dismiss" block:nil];
58 [alert show];
59 [alert release];
60}
61
62///////////////////////////////////////////////////////////////////////////////////////////////////
63#pragma mark - NSObject
64
65- (void)addComponents:(CGRect)frame {
66 if (_title)
67 {
68 CGSize size = [_title sizeWithFont:titleFont
69 constrainedToSize:CGSizeMake(frame.size.width-kAlertViewBorder*2, 1000)
70 lineBreakMode:NSLineBreakByWordWrapping];
71
72 UILabel *labelView = [[UILabel alloc] initWithFrame:CGRectMake(kAlertViewBorder, _height, frame.size.width-kAlertViewBorder*2, size.height)];
73 labelView.font = titleFont;
74 labelView.numberOfLines = 0;
75 labelView.lineBreakMode = NSLineBreakByWordWrapping;
76 labelView.textColor = kAlertViewTitleTextColor;
77 labelView.backgroundColor = [UIColor clearColor];
78 labelView.textAlignment = NSTextAlignmentCenter;
79 labelView.shadowColor = kAlertViewTitleShadowColor;
80 labelView.shadowOffset = kAlertViewTitleShadowOffset;
81 labelView.text = _title;
82 [_view addSubview:labelView];
83 [labelView release];
84
85 _height += size.height + kAlertViewBorder;
86 }
87
88 if (_message)
89 {
90 CGSize size = [_message sizeWithFont:messageFont
91 constrainedToSize:CGSizeMake(frame.size.width-kAlertViewBorder*2, 1000)
92 lineBreakMode:NSLineBreakByWordWrapping];
93
94 UILabel *labelView = [[UILabel alloc] initWithFrame:CGRectMake(kAlertViewBorder, _height, frame.size.width-kAlertViewBorder*2, size.height)];
95 labelView.font = messageFont;
96 labelView.numberOfLines = 0;
97 labelView.lineBreakMode = NSLineBreakByWordWrapping;
98 labelView.textColor = kAlertViewMessageTextColor;
99 labelView.backgroundColor = [UIColor clearColor];
100 labelView.textAlignment = NSTextAlignmentCenter;
101 labelView.shadowColor = kAlertViewMessageShadowColor;
102 labelView.shadowOffset = kAlertViewMessageShadowOffset;
103 labelView.text = _message;
104 [_view addSubview:labelView];
105 [labelView release];
106
107 _height += size.height + kAlertViewBorder;
108 }
109}
110
111- (void)setupDisplay
112{
113 [[_view subviews] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
114 [obj removeFromSuperview];
115 }];
116
117 UIWindow *parentView = [BlockBackground sharedInstance];
118 CGRect frame = parentView.bounds;
119 frame.origin.x = floorf((frame.size.width - background.size.width) * 0.5);
120 frame.size.width = background.size.width;
121
122 UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
123 if (UIInterfaceOrientationIsLandscape(orientation)) {
124 frame.size.width += 150;
125 frame.origin.x -= 75;
126 }
127
128 _view.frame = frame;
129
130 _height = kAlertViewBorder + 15;
131
132 if (NeedsLandscapePhoneTweaks) {
133 _height -= 15; // landscape phones need to trimmed a bit
134 }
135
136 [self addComponents:frame];
137
138 if (_shown)
139 [self show];
140}
141
142- (id)initWithTitle:(NSString *)title message:(NSString *)message
143{
144 self = [super init];
145
146 if (self)
147 {
148 _title = [title copy];
149 _message = [message copy];
150
151 _view = [[UIView alloc] init];
152
153 _view.autoresizingMask = UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin;
154
155 _blocks = [[NSMutableArray alloc] init];
156
157 [[NSNotificationCenter defaultCenter] addObserver:self
158 selector:@selector(setupDisplay)
159 name:UIApplicationDidChangeStatusBarOrientationNotification
160 object:nil];
161
162 if ([self class] == [BlockAlertView class])
163 [self setupDisplay];
164
165 _vignetteBackground = NO;
166 }
167
168 return self;
169}
170
171- (void)dealloc
172{
173 [_title release];
174 [_message release];
175 [_backgroundImage release];
176 [_view release];
177 [_blocks release];
178 [super dealloc];
179}
180
181///////////////////////////////////////////////////////////////////////////////////////////////////
182#pragma mark - Public
183
184- (void)addButtonWithTitle:(NSString *)title color:(NSString*)color block:(void (^)())block
185{
186 [_blocks addObject:[NSArray arrayWithObjects:
187 block ? [[block copy] autorelease] : [NSNull null],
188 title,
189 color,
190 nil]];
191}
192
193- (void)addButtonWithTitle:(NSString *)title block:(void (^)())block
194{
195 [self addButtonWithTitle:title color:@"gray" block:block];
196}
197
198- (void)setCancelButtonWithTitle:(NSString *)title block:(void (^)())block
199{
200 [self addButtonWithTitle:title color:@"black" block:block];
201}
202
203- (void)setDestructiveButtonWithTitle:(NSString *)title block:(void (^)())block
204{
205 [self addButtonWithTitle:title color:@"red" block:block];
206}
207
208- (void)addButtonWithTitle:(NSString *)title imageIdentifier:(NSString*)identifier block:(void (^)())block {
209 [self addButtonWithTitle:title color:identifier block:block];
210}
211
212- (void)show
213{
214 _shown = YES;
215
216 BOOL isSecondButton = NO;
217 NSUInteger index = 0;
218 for (NSUInteger i = 0; i < _blocks.count; i++)
219 {
220 NSArray *block = [_blocks objectAtIndex:i];
221 NSString *title = [block objectAtIndex:1];
222 NSString *color = [block objectAtIndex:2];
223
224 UIImage *image = [UIImage imageNamed:[NSString stringWithFormat:@"alert-%@-button.png", color]];
225 image = [image stretchableImageWithLeftCapWidth:(int)(image.size.width+1)>>1 topCapHeight:0];
226
227 UIImage *highlightedImage = [UIImage imageNamed:[NSString stringWithFormat:@"alert-%@-button-highlighted.png", color]];
228
229 highlightedImage = [highlightedImage stretchableImageWithLeftCapWidth:(int)(highlightedImage.size.width+1)>>1 topCapHeight:0];
230
231 CGFloat maxHalfWidth = floorf((_view.bounds.size.width-kAlertViewBorder*3)*0.5);
232 CGFloat width = _view.bounds.size.width-kAlertViewBorder*2;
233 CGFloat xOffset = kAlertViewBorder;
234 if (isSecondButton)
235 {
236 width = maxHalfWidth;
237 xOffset = width + kAlertViewBorder * 2;
238 isSecondButton = NO;
239 }
240 else if (i + 1 < _blocks.count)
241 {
242 // In this case there's another button.
243 // Let's check if they fit on the same line.
244 CGSize size = [title sizeWithFont:buttonFont
245 minFontSize:10
246 actualFontSize:nil
247 forWidth:_view.bounds.size.width-kAlertViewBorder*2
248 lineBreakMode:NSLineBreakByClipping];
249
250 if (size.width < maxHalfWidth - kAlertViewBorder)
251 {
252 // It might fit. Check the next Button
253 NSArray *block2 = [_blocks objectAtIndex:i+1];
254 NSString *title2 = [block2 objectAtIndex:1];
255 size = [title2 sizeWithFont:buttonFont
256 minFontSize:10
257 actualFontSize:nil
258 forWidth:_view.bounds.size.width-kAlertViewBorder*2
259 lineBreakMode:NSLineBreakByClipping];
260
261 if (size.width < maxHalfWidth - kAlertViewBorder)
262 {
263 // They'll fit!
264 isSecondButton = YES; // For the next iteration
265 width = maxHalfWidth;
266 }
267 }
268 }
269 else if (_blocks.count == 1)
270 {
271 // In this case this is the ony button. We'll size according to the text
272 CGSize size = [title sizeWithFont:buttonFont
273 minFontSize:10
274 actualFontSize:nil
275 forWidth:_view.bounds.size.width-kAlertViewBorder*2
276 lineBreakMode:NSLineBreakByClipping];
277
278 size.width = MAX(size.width, 80);
279 if (size.width + 2 * kAlertViewBorder < width)
280 {
281 width = size.width + 2 * kAlertViewBorder;
282 xOffset = floorf((_view.bounds.size.width - width) * 0.5);
283 }
284 }
285
286 UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
287 button.frame = CGRectMake(xOffset, _height, width, kAlertButtonHeight);
288 button.titleLabel.font = buttonFont;
289 if (IOS_LESS_THAN_6) {
290#pragma clan diagnostic push
291#pragma clang diagnostic ignored "-Wdeprecated-declarations"
292 button.titleLabel.minimumFontSize = 10;
293#pragma clan diagnostic pop
294 }
295 else {
296 button.titleLabel.adjustsFontSizeToFitWidth = YES;
297 button.titleLabel.adjustsLetterSpacingToFitWidth = YES;
298 button.titleLabel.minimumScaleFactor = 0.1;
299 }
300 button.titleLabel.textAlignment = NSTextAlignmentCenter;
301 button.titleLabel.shadowOffset = kAlertViewButtonShadowOffset;
302 button.backgroundColor = [UIColor clearColor];
303 button.tag = i+1;
304
305 [button setBackgroundImage:image forState:UIControlStateNormal];
306 if (highlightedImage)
307 {
308 [button setBackgroundImage:highlightedImage forState:UIControlStateHighlighted];
309 }
310 [button setTitleColor:kAlertViewButtonTextColor forState:UIControlStateNormal];
311 [button setTitleShadowColor:kAlertViewButtonShadowColor forState:UIControlStateNormal];
312 [button setTitle:title forState:UIControlStateNormal];
313 button.accessibilityLabel = title;
314
315 [button addTarget:self action:@selector(buttonClicked:) forControlEvents:UIControlEventTouchUpInside];
316
317 [_view addSubview:button];
318
319 if (!isSecondButton)
320 _height += kAlertButtonHeight + kAlertViewBorder;
321
322 index++;
323 }
324
325 //_height += 10; // Margin for the shadow // not sure where this came from, but it's making things look strange (I don't see a shadow, either)
326
327 if (_height < background.size.height)
328 {
329 CGFloat offset = background.size.height - _height;
330 _height = background.size.height;
331 CGRect frame;
332 for (NSUInteger i = 0; i < _blocks.count; i++)
333 {
334 UIButton *btn = (UIButton *)[_view viewWithTag:i+1];
335 frame = btn.frame;
336 frame.origin.y += offset;
337 btn.frame = frame;
338 }
339 }
340
341
342 CGRect frame = _view.frame;
343 frame.origin.y = - _height;
344 frame.size.height = _height;
345 _view.frame = frame;
346
347 UIImageView *modalBackground = [[UIImageView alloc] initWithFrame:_view.bounds];
348
349 if (UIInterfaceOrientationIsLandscape([[UIApplication sharedApplication] statusBarOrientation]))
350 modalBackground.image = backgroundlandscape;
351 else
352 modalBackground.image = background;
353
354 modalBackground.contentMode = UIViewContentModeScaleToFill;
355 modalBackground.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
356 [_view insertSubview:modalBackground atIndex:0];
357 [modalBackground release];
358
359 if (_backgroundImage)
360 {
361 [BlockBackground sharedInstance].backgroundImage = _backgroundImage;
362 [_backgroundImage release];
363 _backgroundImage = nil;
364 }
365
366 [BlockBackground sharedInstance].vignetteBackground = _vignetteBackground;
367 [[BlockBackground sharedInstance] addToMainWindow:_view];
368
369 __block CGPoint center = _view.center;
370 center.y = floorf([BlockBackground sharedInstance].bounds.size.height * 0.5) + kAlertViewBounce;
371
372 _cancelBounce = NO;
373
374 [UIView animateWithDuration:0.4
375 delay:0.0
376 options:UIViewAnimationOptionCurveEaseOut
377 animations:^{
378 [BlockBackground sharedInstance].alpha = 1.0f;
379 _view.center = center;
380 }
381 completion:^(BOOL finished) {
382 if (_cancelBounce) return;
383
384 [UIView animateWithDuration:0.1
385 delay:0.0
386 options:0
387 animations:^{
388 center.y -= kAlertViewBounce;
389 _view.center = center;
390 }
391 completion:^(BOOL finished) {
392 [[NSNotificationCenter defaultCenter] postNotificationName:@"AlertViewFinishedAnimations" object:self];
393 }];
394 }];
395
396 [self retain];
397}
398
399- (void)dismissWithClickedButtonIndex:(NSInteger)buttonIndex animated:(BOOL)animated
400{
401 _shown = NO;
402
403 [[NSNotificationCenter defaultCenter] removeObserver:self];
404
405 if (buttonIndex >= 0 && buttonIndex < [_blocks count])
406 {
407 id obj = [[_blocks objectAtIndex: buttonIndex] objectAtIndex:0];
408 if (![obj isEqual:[NSNull null]])
409 {
410 ((void (^)())obj)();
411 }
412 }
413
414 if (animated)
415 {
416 [UIView animateWithDuration:0.1
417 delay:0.0
418 options:0
419 animations:^{
420 CGPoint center = _view.center;
421 center.y += 20;
422 _view.center = center;
423 }
424 completion:^(BOOL finished) {
425 [UIView animateWithDuration:0.4
426 delay:0.0
427 options:UIViewAnimationOptionCurveEaseIn
428 animations:^{
429 CGRect frame = _view.frame;
430 frame.origin.y = -frame.size.height;
431 _view.frame = frame;
432 [[BlockBackground sharedInstance] reduceAlphaIfEmpty];
433 }
434 completion:^(BOOL finished) {
435 [[BlockBackground sharedInstance] removeView:_view];
436 [_view release]; _view = nil;
437 [self autorelease];
438 }];
439 }];
440 }
441 else
442 {
443 [[BlockBackground sharedInstance] removeView:_view];
444 [_view release]; _view = nil;
445 [self autorelease];
446 }
447}
448
449///////////////////////////////////////////////////////////////////////////////////////////////////
450#pragma mark - Action
451
452- (void)buttonClicked:(id)sender
453{
454 /* Run the button's block */
455 int buttonIndex = [(UIButton *)sender tag] - 1;
456 [self dismissWithClickedButtonIndex:buttonIndex animated:YES];
457}
458
459@end