FreeRDP
Toast+UIView.m
1 /***************************************************************************
2 
3  Toast+UIView.h
4  Toast
5  Version 0.1
6 
7  Copyright (c) 2011 Charles Scalesse.
8 
9  Permission is hereby granted, free of charge, to any person obtaining a
10  copy of this software and associated documentation files (the
11  "Software"), to deal in the Software without restriction, including
12  without limitation the rights to use, copy, modify, merge, publish,
13  distribute, sublicense, and/or sell copies of the Software, and to
14  permit persons to whom the Software is furnished to do so, subject to
15  the following conditions:
16 
17  The above copyright notice and this permission notice shall be included
18  in all copies or substantial portions of the Software.
19 
20  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
21  OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
23  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
24  CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
25  TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
26  SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27 
28  ***************************************************************************/
29 
30 #import "Toast+UIView.h"
31 #import <QuartzCore/QuartzCore.h>
32 #import <objc/runtime.h>
33 
34 #define kMaxWidth 0.8
35 #define kMaxHeight 0.8
36 
37 #define kHorizontalPadding 10.0
38 #define kVerticalPadding 10.0
39 #define kCornerRadius 10.0
40 #define kOpacity 0.8
41 #define kFontSize 16.0
42 #define kMaxTitleLines 999
43 #define kMaxMessageLines 999
44 
45 #define kFadeDuration 0.2
46 
47 #define kDefaultLength 3
48 #define kDefaultPosition @"bottom"
49 
50 #define kImageWidth 80.0
51 #define kImageHeight 80.0
52 
53 static NSString *kDurationKey = @"duration";
54 
55 @interface UIView (ToastPrivate)
56 
57 - (CGPoint)getPositionFor:(id)position toast:(UIView *)toast;
58 - (UIView *)makeViewForMessage:(NSString *)message title:(NSString *)title image:(UIImage *)image;
59 
60 @end
61 
62 @implementation UIView (Toast)
63 
64 #pragma mark -
65 #pragma mark Toast Methods
66 
67 - (void)makeToast:(NSString *)message
68 {
69  [self makeToast:message duration:kDefaultLength position:kDefaultPosition];
70 }
71 
72 - (void)makeToast:(NSString *)message duration:(float)interval position:(id)point
73 {
74  UIView *toast = [self makeViewForMessage:message title:nil image:nil];
75  [self showToast:toast duration:interval position:point];
76 }
77 
78 - (void)makeToast:(NSString *)message
79  duration:(float)interval
80  position:(id)point
81  title:(NSString *)title
82 {
83  UIView *toast = [self makeViewForMessage:message title:title image:nil];
84  [self showToast:toast duration:interval position:point];
85 }
86 
87 - (void)makeToast:(NSString *)message
88  duration:(float)interval
89  position:(id)point
90  image:(UIImage *)image
91 {
92  UIView *toast = [self makeViewForMessage:message title:nil image:image];
93  [self showToast:toast duration:interval position:point];
94 }
95 
96 - (void)makeToast:(NSString *)message
97  duration:(float)interval
98  position:(id)point
99  title:(NSString *)title
100  image:(UIImage *)image
101 {
102  UIView *toast = [self makeViewForMessage:message title:title image:image];
103  [self showToast:toast duration:interval position:point];
104 }
105 
106 - (void)showToast:(UIView *)toast
107 {
108  [self showToast:toast duration:kDefaultLength position:kDefaultPosition];
109 }
110 
111 - (void)showToast:(UIView *)toast duration:(float)interval position:(id)point
112 {
113 
114  /****************************************************
115  * *
116  * Displays a view for a given duration & position. *
117  * *
118  ****************************************************/
119 
120  CGPoint toastPoint = [self getPositionFor:point toast:toast];
121 
122  // use an associative reference to associate the toast view with the display interval
123  objc_setAssociatedObject(toast, &kDurationKey, [NSNumber numberWithFloat:interval],
124  OBJC_ASSOCIATION_RETAIN);
125 
126  [toast setCenter:toastPoint];
127  [toast setAlpha:0.0];
128  [self addSubview:toast];
129 
130  [UIView beginAnimations:@"fade_in" context:toast];
131  [UIView setAnimationDuration:kFadeDuration];
132  [UIView setAnimationDelegate:self];
133  [UIView setAnimationDidStopSelector:@selector(animationDidStop:finished:context:)];
134  [UIView setAnimationCurve:UIViewAnimationCurveEaseOut];
135  [toast setAlpha:1.0];
136  [UIView commitAnimations];
137 }
138 
139 #pragma mark -
140 #pragma mark Animation Delegate Method
141 
142 - (void)animationDidStop:(NSString *)animationID finished:(BOOL)finished context:(void *)context
143 {
144 
145  UIView *toast = (UIView *)context;
146 
147  // retrieve the display interval associated with the view
148  float interval = [(NSNumber *)objc_getAssociatedObject(toast, &kDurationKey) floatValue];
149 
150  if ([animationID isEqualToString:@"fade_in"])
151  {
152 
153  [UIView beginAnimations:@"fade_out" context:toast];
154  [UIView setAnimationDelay:interval];
155  [UIView setAnimationDuration:kFadeDuration];
156  [UIView setAnimationDelegate:self];
157  [UIView setAnimationDidStopSelector:@selector(animationDidStop:finished:context:)];
158  [UIView setAnimationCurve:UIViewAnimationCurveEaseIn];
159  [toast setAlpha:0.0];
160  [UIView commitAnimations];
161  }
162  else if ([animationID isEqualToString:@"fade_out"])
163  {
164 
165  [toast removeFromSuperview];
166  }
167 }
168 
169 #pragma mark -
170 #pragma mark Private Methods
171 
172 - (CGPoint)getPositionFor:(id)point toast:(UIView *)toast
173 {
174 
175  /*************************************************************************************
176  * *
177  * Converts string literals @"top", @"bottom", @"center", or any point wrapped in an *
178  * NSValue object into a CGPoint *
179  * *
180  *************************************************************************************/
181 
182  if ([point isKindOfClass:[NSString class]])
183  {
184 
185  if ([point caseInsensitiveCompare:@"top"] == NSOrderedSame)
186  {
187  return CGPointMake(self.bounds.size.width / 2,
188  (toast.frame.size.height / 2) + kVerticalPadding);
189  }
190  else if ([point caseInsensitiveCompare:@"bottom"] == NSOrderedSame)
191  {
192  return CGPointMake(self.bounds.size.width / 2,
193  (self.bounds.size.height - (toast.frame.size.height / 2)) -
194  kVerticalPadding);
195  }
196  else if ([point caseInsensitiveCompare:@"center"] == NSOrderedSame)
197  {
198  return CGPointMake(self.bounds.size.width / 2, self.bounds.size.height / 2);
199  }
200  }
201  else if ([point isKindOfClass:[NSValue class]])
202  {
203  return [point CGPointValue];
204  }
205 
206  NSLog(@"Error: Invalid position for toast.");
207  return [self getPositionFor:kDefaultPosition toast:toast];
208 }
209 
210 - (UIView *)makeViewForMessage:(NSString *)message title:(NSString *)title image:(UIImage *)image
211 {
212 
213  /***********************************************************************************
214  * *
215  * Dynamically build a toast view with any combination of message, title, & image. *
216  * *
217  ***********************************************************************************/
218 
219  if ((message == nil) && (title == nil) && (image == nil))
220  return nil;
221 
222  UILabel *messageLabel = nil;
223  UILabel *titleLabel = nil;
224  UIImageView *imageView = nil;
225 
226  // create the parent view
227  UIView *wrapperView = [[[UIView alloc] init] autorelease];
228  [wrapperView.layer setCornerRadius:kCornerRadius];
229  [wrapperView setBackgroundColor:[[UIColor blackColor] colorWithAlphaComponent:kOpacity]];
230  wrapperView.autoresizingMask =
231  UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleLeftMargin |
232  UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleBottomMargin;
233 
234  if (image != nil)
235  {
236  imageView = [[[UIImageView alloc] initWithImage:image] autorelease];
237  [imageView setContentMode:UIViewContentModeScaleAspectFit];
238  [imageView
239  setFrame:CGRectMake(kHorizontalPadding, kVerticalPadding, kImageWidth, kImageHeight)];
240  }
241 
242  float imageWidth, imageHeight, imageLeft;
243 
244  // the imageView frame values will be used to size & position the other views
245  if (imageView != nil)
246  {
247  imageWidth = imageView.bounds.size.width;
248  imageHeight = imageView.bounds.size.height;
249  imageLeft = kHorizontalPadding;
250  }
251  else
252  {
253  imageWidth = imageHeight = imageLeft = 0;
254  }
255 
256  if (title != nil)
257  {
258  titleLabel = [[[UILabel alloc] init] autorelease];
259  [titleLabel setNumberOfLines:kMaxTitleLines];
260  [titleLabel setFont:[UIFont boldSystemFontOfSize:kFontSize]];
261  [titleLabel setTextAlignment:UITextAlignmentLeft];
262  [titleLabel setLineBreakMode:UILineBreakModeWordWrap];
263  [titleLabel setTextColor:[UIColor whiteColor]];
264  [titleLabel setBackgroundColor:[UIColor clearColor]];
265  [titleLabel setAlpha:1.0];
266  [titleLabel setText:title];
267 
268  // size the title label according to the length of the text
269  CGSize maxSizeTitle = CGSizeMake((self.bounds.size.width * kMaxWidth) - imageWidth,
270  self.bounds.size.height * kMaxHeight);
271  CGSize expectedSizeTitle = [title sizeWithFont:titleLabel.font
272  constrainedToSize:maxSizeTitle
273  lineBreakMode:titleLabel.lineBreakMode];
274  [titleLabel setFrame:CGRectMake(0, 0, expectedSizeTitle.width, expectedSizeTitle.height)];
275  }
276 
277  if (message != nil)
278  {
279  messageLabel = [[[UILabel alloc] init] autorelease];
280  [messageLabel setNumberOfLines:kMaxMessageLines];
281  [messageLabel setFont:[UIFont systemFontOfSize:kFontSize]];
282  [messageLabel setLineBreakMode:UILineBreakModeWordWrap];
283  [messageLabel setTextColor:[UIColor whiteColor]];
284  [messageLabel setTextAlignment:UITextAlignmentCenter];
285  [messageLabel setBackgroundColor:[UIColor clearColor]];
286  [messageLabel setAlpha:1.0];
287  [messageLabel setText:message];
288 
289  // size the message label according to the length of the text
290  CGSize maxSizeMessage = CGSizeMake((self.bounds.size.width * kMaxWidth) - imageWidth,
291  self.bounds.size.height * kMaxHeight);
292  CGSize expectedSizeMessage = [message sizeWithFont:messageLabel.font
293  constrainedToSize:maxSizeMessage
294  lineBreakMode:messageLabel.lineBreakMode];
295  [messageLabel
296  setFrame:CGRectMake(0, 0, expectedSizeMessage.width, expectedSizeMessage.height)];
297  }
298 
299  // titleLabel frame values
300  float titleWidth, titleHeight, titleTop, titleLeft;
301 
302  if (titleLabel != nil)
303  {
304  titleWidth = titleLabel.bounds.size.width;
305  titleHeight = titleLabel.bounds.size.height;
306  titleTop = kVerticalPadding;
307  titleLeft = imageLeft + imageWidth + kHorizontalPadding;
308  }
309  else
310  {
311  titleWidth = titleHeight = titleTop = titleLeft = 0;
312  }
313 
314  // messageLabel frame values
315  float messageWidth, messageHeight, messageLeft, messageTop;
316 
317  if (messageLabel != nil)
318  {
319  messageWidth = messageLabel.bounds.size.width;
320  messageHeight = messageLabel.bounds.size.height;
321  messageLeft = imageLeft + imageWidth + kHorizontalPadding;
322  messageTop = titleTop + titleHeight + kVerticalPadding;
323  }
324  else
325  {
326  messageWidth = messageHeight = messageLeft = messageTop = 0;
327  }
328 
329  // compare the title & message widths and use the longer value to calculate the size of the
330  // wrapper width the same logic applies to the x value (left)
331  float longerWidth = (messageWidth < titleWidth) ? titleWidth : messageWidth;
332  float longerLeft = (messageLeft < titleLeft) ? titleLeft : messageLeft;
333 
334  // if the image width is larger than longerWidth, use the image width to calculate the wrapper
335  // width. the same logic applies to the wrapper height
336  float wrapperWidth =
337  ((longerLeft + longerWidth + kHorizontalPadding) < imageWidth + (kHorizontalPadding * 2))
338  ? imageWidth + (kHorizontalPadding * 2)
339  : (longerLeft + longerWidth + kHorizontalPadding);
340  float wrapperHeight =
341  ((messageTop + messageHeight + kVerticalPadding) < imageHeight + (kVerticalPadding * 2))
342  ? imageHeight + (kVerticalPadding * 2)
343  : (messageTop + messageHeight + kVerticalPadding);
344 
345  [wrapperView setFrame:CGRectMake(0, 0, wrapperWidth, wrapperHeight)];
346 
347  if (titleLabel != nil)
348  {
349  [titleLabel setFrame:CGRectMake(titleLeft, titleTop, titleWidth, titleHeight)];
350  [wrapperView addSubview:titleLabel];
351  }
352 
353  if (messageLabel != nil)
354  {
355  [messageLabel setFrame:CGRectMake(messageLeft, messageTop, messageWidth, messageHeight)];
356  [wrapperView addSubview:messageLabel];
357  }
358 
359  if (imageView != nil)
360  {
361  [wrapperView addSubview:imageView];
362  }
363 
364  return wrapperView;
365 }
366 
367 @end