master
1//
2// TSMessageView.m
3// Toursprung
4//
5// Created by Felix Krause on 24.08.12.
6// Copyright (c) 2012 Toursprung. All rights reserved.
7//
8
9#import "TSMessageView.h"
10#import "UIColor+MLColorAdditions.h"
11
12#define TSMessageViewPadding 15.0
13
14#define TSDesignFileName @"design.json"
15
16static NSDictionary *notificationDesign;
17
18@interface TSMessageView ()
19
20@property (nonatomic, strong) NSString *title;
21@property (nonatomic, strong) NSString *content;
22@property (nonatomic, strong) NSString *buttonTitle;
23@property (nonatomic, strong) UIViewController *viewController;
24
25/** Internal properties needed to resize the view on device rotation properly */
26@property (nonatomic, strong) UILabel *titleLabel;
27@property (nonatomic, strong) UILabel *contentLabel;
28@property (nonatomic, strong) UIImageView *iconImageView;
29@property (nonatomic, strong) UIButton *button;
30@property (nonatomic, strong) UIView *borderView;
31@property (nonatomic, strong) UIImageView *backgroundImageView;
32
33@property (nonatomic, assign) CGFloat textSpaceLeft;
34@property (nonatomic, assign) CGFloat textSpaceRight;
35
36@property (copy) void (^callback)();
37@property (copy) void (^buttonCallback)();
38
39- (CGFloat)updateHeightOfMessageView;
40- (void)layoutSubviews;
41
42@end
43
44
45@implementation TSMessageView
46
47- (id)initWithTitle:(NSString *)title
48 withContent:(NSString *)content
49 withType:(TSMessageNotificationType)notificationType
50 withDuration:(CGFloat)duration
51 inViewController:(UIViewController *)viewController
52 withCallback:(void (^)())callback
53 withButtonTitle:(NSString *)buttonTitle
54 withButtonCallback:(void (^)())buttonCallback
55 atPosition:(TSMessageNotificationPosition)position
56 shouldBeDismissed:(BOOL)dismissAble
57{
58 if (!notificationDesign)
59 {
60 NSString *path = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:TSDesignFileName];
61 notificationDesign = [NSJSONSerialization JSONObjectWithData:[NSData dataWithContentsOfFile:path]
62 options:kNilOptions
63 error:nil];
64 }
65
66 if ((self = [self init]))
67 {
68 _title = title;
69 _content = content;
70 _buttonTitle = buttonTitle;
71 _duration = duration;
72 _viewController = viewController;
73 _messagePosition = position;
74 self.callback = callback;
75 self.buttonCallback = buttonCallback;
76
77 CGFloat screenWidth = self.viewController.view.bounds.size.width;
78 NSDictionary *current;
79 NSString *currentString;
80 switch (notificationType)
81 {
82 case TSMessageNotificationTypeMessage:
83 {
84 currentString = @"message";
85 break;
86 }
87 case TSMessageNotificationTypeError:
88 {
89 currentString = @"error";
90 break;
91 }
92 case TSMessageNotificationTypeSuccess:
93 {
94 currentString = @"success";
95 break;
96 }
97 case TSMessageNotificationTypeWarning:
98 {
99 currentString = @"warning";
100 break;
101 }
102
103 default:
104 break;
105 }
106
107 current = [notificationDesign valueForKey:currentString];
108
109 self.alpha = 0.0;
110
111 UIImage *image;
112 if ([current valueForKey:@"imageName"])
113 {
114 image = [UIImage imageNamed:[current valueForKey:@"imageName"]];
115 }
116
117 // add background image here
118 UIImage *backgroundImage = [[UIImage imageNamed:[current valueForKey:@"backgroundImageName"]] stretchableImageWithLeftCapWidth:0.0 topCapHeight:0.0];
119 _backgroundImageView = [[UIImageView alloc] initWithImage:backgroundImage];
120 self.backgroundImageView.autoresizingMask = (UIViewAutoresizingFlexibleWidth);
121 [self addSubview:self.backgroundImageView];
122
123 UIColor *fontColor = [UIColor colorWithHexString:[current valueForKey:@"textColor"]
124 alpha:1.0];
125
126
127 self.textSpaceLeft = 2 * TSMessageViewPadding;
128 if (image) self.textSpaceLeft += image.size.width + 2 * TSMessageViewPadding;
129
130 // Set up title label
131 _titleLabel = [[UILabel alloc] init];
132 [self.titleLabel setText:title];
133 [self.titleLabel setTextColor:fontColor];
134 [self.titleLabel setBackgroundColor:[UIColor clearColor]];
135 [self.titleLabel setFont:[UIFont boldSystemFontOfSize:[[current valueForKey:@"titleFontSize"] floatValue]]];
136 [self.titleLabel setShadowColor:[UIColor colorWithHexString:[current valueForKey:@"shadowColor"] alpha:1.0]];
137 [self.titleLabel setShadowOffset:CGSizeMake([[current valueForKey:@"shadowOffsetX"] floatValue],
138 [[current valueForKey:@"shadowOffsetY"] floatValue])];
139 self.titleLabel.numberOfLines = 0;
140 self.titleLabel.lineBreakMode = NSLineBreakByWordWrapping;
141 [self addSubview:self.titleLabel];
142
143 // Set up content label (if set)
144 if ([content length])
145 {
146 _contentLabel = [[UILabel alloc] init];
147 [self.contentLabel setText:content];
148
149 UIColor *contentTextColor = [UIColor colorWithHexString:[current valueForKey:@"contentTextColor"] alpha:1.0];
150 if (!contentTextColor)
151 {
152 contentTextColor = fontColor;
153 }
154 [self.contentLabel setTextColor:contentTextColor];
155 [self.contentLabel setBackgroundColor:[UIColor clearColor]];
156 [self.contentLabel setFont:[UIFont systemFontOfSize:[[current valueForKey:@"contentFontSize"] floatValue]]];
157 [self.contentLabel setShadowColor:self.titleLabel.shadowColor];
158 [self.contentLabel setShadowOffset:self.titleLabel.shadowOffset];
159 self.contentLabel.lineBreakMode = self.titleLabel.lineBreakMode;
160 self.contentLabel.numberOfLines = 0;
161
162 [self addSubview:self.contentLabel];
163 }
164
165 if (image)
166 {
167 _iconImageView = [[UIImageView alloc] initWithImage:image];
168 self.iconImageView.frame = CGRectMake(TSMessageViewPadding * 2,
169 TSMessageViewPadding,
170 image.size.width,
171 image.size.height);
172 [self addSubview:self.iconImageView];
173 }
174
175 // Set up button (if set)
176 if ([buttonTitle length])
177 {
178 _button = [UIButton buttonWithType:UIButtonTypeCustom];
179
180 UIImage *buttonBackgroundImage = [[UIImage imageNamed:[current valueForKey:@"buttonBackgroundImageName"]] resizableImageWithCapInsets:UIEdgeInsetsMake(15.0, 12.0, 15.0, 11.0)];
181
182 if (!buttonBackgroundImage)
183 {
184 buttonBackgroundImage = [[UIImage imageNamed:[current valueForKey:@"NotificationButtonBackground"]] resizableImageWithCapInsets:UIEdgeInsetsMake(15.0, 12.0, 15.0, 11.0)];
185 }
186
187 [self.button setBackgroundImage:buttonBackgroundImage forState:UIControlStateNormal];
188 [self.button setTitle:self.buttonTitle forState:UIControlStateNormal];
189
190 UIColor *buttonTitleShadowColor = [UIColor colorWithHexString:[current valueForKey:@"buttonTitleShadowColor"] alpha:1.0];
191 if (!buttonTitleShadowColor)
192 {
193 buttonTitleShadowColor = self.titleLabel.shadowColor;
194 }
195
196 [self.button setTitleShadowColor:buttonTitleShadowColor forState:UIControlStateNormal];
197
198 UIColor *buttonTitleTextColor = [UIColor colorWithHexString:[current valueForKey:@"buttonTitleTextColor"] alpha:1.0];
199 if (!buttonTitleTextColor)
200 {
201 buttonTitleTextColor = fontColor;
202 }
203
204 [self.button setTitleColor:buttonTitleTextColor forState:UIControlStateNormal];
205 self.button.titleLabel.font = [UIFont boldSystemFontOfSize:14.0];
206 self.button.titleLabel.shadowOffset = CGSizeMake([[current valueForKey:@"buttonTitleShadowOffsetX"] floatValue],
207 [[current valueForKey:@"buttonTitleShadowOffsetY"] floatValue]);
208 [self.button addTarget:self
209 action:@selector(buttonTapped:)
210 forControlEvents:UIControlEventTouchUpInside];
211
212 self.button.contentEdgeInsets = UIEdgeInsetsMake(0.0, 5.0, 0.0, 5.0);
213 [self.button sizeToFit];
214 self.button.frame = CGRectMake(screenWidth - TSMessageViewPadding - self.button.frame.size.width,
215 0.0,
216 self.button.frame.size.width,
217 31.0);
218
219 [self addSubview:self.button];
220
221 self.textSpaceRight = self.button.frame.size.width + TSMessageViewPadding;
222 }
223
224 // Add a border on the bottom (or on the top, depending on the view's postion)
225 _borderView = [[UIView alloc] initWithFrame:CGRectMake(0.0,
226 0.0, // will be set later
227 screenWidth,
228 [[current valueForKey:@"borderHeight"] floatValue])];
229 self.borderView.backgroundColor = [UIColor colorWithHexString:[current valueForKey:@"borderColor"]
230 alpha:1.0];
231 self.borderView.autoresizingMask = (UIViewAutoresizingFlexibleWidth);
232 [self addSubview:self.borderView];
233
234
235 CGFloat actualHeight = [self updateHeightOfMessageView]; // this call also takes care of positioning the labels
236 CGFloat topPosition = -actualHeight;
237
238 if (self.messagePosition == TSMessageNotificationPositionBottom)
239 {
240 topPosition = self.viewController.view.bounds.size.height;
241 }
242
243 self.frame = CGRectMake(0.0, topPosition, screenWidth, actualHeight);
244
245 if (self.messagePosition == TSMessageNotificationPositionTop)
246 {
247 self.autoresizingMask = UIViewAutoresizingFlexibleWidth;
248 }
249 else
250 {
251 self.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin);
252 }
253
254 if (dismissAble)
255 {
256 UISwipeGestureRecognizer *gestureRec = [[UISwipeGestureRecognizer alloc] initWithTarget:self
257 action:@selector(fadeMeOut)];
258 [gestureRec setDirection:(self.messagePosition == TSMessageNotificationPositionTop ?
259 UISwipeGestureRecognizerDirectionUp :
260 UISwipeGestureRecognizerDirectionDown)];
261 [self addGestureRecognizer:gestureRec];
262
263 UITapGestureRecognizer *tapRec = [[UITapGestureRecognizer alloc] initWithTarget:self
264 action:@selector(fadeMeOut)];
265 [self addGestureRecognizer:tapRec];
266 }
267 }
268 return self;
269}
270
271
272- (CGFloat)updateHeightOfMessageView
273{
274 CGFloat currentHeight;
275 CGFloat screenWidth = self.viewController.view.bounds.size.width;
276
277
278 self.titleLabel.frame = CGRectMake(self.textSpaceLeft,
279 TSMessageViewPadding,
280 screenWidth - TSMessageViewPadding - self.textSpaceLeft - self.textSpaceRight,
281 0.0);
282 [self.titleLabel sizeToFit];
283
284 if ([self.content length])
285 {
286 self.contentLabel.frame = CGRectMake(self.textSpaceLeft,
287 self.titleLabel.frame.origin.y + self.titleLabel.frame.size.height + 5.0,
288 screenWidth - TSMessageViewPadding - self.textSpaceLeft - self.textSpaceRight,
289 0.0);
290 [self.contentLabel sizeToFit];
291
292 currentHeight = self.contentLabel.frame.origin.y + self.contentLabel.frame.size.height;
293 }
294 else
295 {
296 // only the title was set
297 currentHeight = self.titleLabel.frame.origin.y + self.titleLabel.frame.size.height;
298 }
299
300 currentHeight += TSMessageViewPadding;
301
302 if (self.iconImageView)
303 {
304 // Check if that makes the popup larger (height)
305 if (self.iconImageView.frame.origin.y + self.iconImageView.frame.size.height + TSMessageViewPadding > currentHeight)
306 {
307 currentHeight = self.iconImageView.frame.origin.y + self.iconImageView.frame.size.height;
308 }
309 else
310 {
311 // z-align
312 self.iconImageView.center = CGPointMake([self.iconImageView center].x,
313 round(currentHeight / 2.0));
314 }
315 }
316
317 // z-align button
318 self.button.center = CGPointMake([self.button center].x,
319 round(currentHeight / 2.0));
320
321 if (self.messagePosition == TSMessageNotificationPositionTop)
322 {
323 // Correct the border position
324 CGRect borderFrame = self.borderView.frame;
325 borderFrame.origin.y = currentHeight;
326 self.borderView.frame = borderFrame;
327 }
328
329 currentHeight += self.borderView.frame.size.height;
330
331 self.frame = CGRectMake(0.0, self.frame.origin.y, self.frame.size.width, currentHeight);
332
333
334 if (self.button)
335 {
336 self.button.frame = CGRectMake(self.frame.size.width - self.textSpaceRight,
337 round((self.frame.size.height / 2.0) - self.button.frame.size.height / 2.0),
338 self.button.frame.size.width,
339 self.button.frame.size.height);
340 }
341
342
343 self.backgroundImageView.frame = CGRectMake(self.backgroundImageView.frame.origin.x,
344 self.backgroundImageView.frame.origin.y,
345 screenWidth,
346 currentHeight);
347
348 return currentHeight;
349}
350
351- (void)layoutSubviews
352{
353 [super layoutSubviews];
354 [self updateHeightOfMessageView];
355}
356
357- (void)fadeMeOut
358{
359 // user tapped on the message
360 dispatch_async(dispatch_get_main_queue(), ^
361 {
362 if (self.callback)
363 {
364 self.callback();
365 }
366
367 [[TSMessage sharedMessage] performSelector:@selector(fadeOutNotification:)
368 withObject:self];
369 });
370}
371
372#pragma mark - UIButton target
373
374- (void)buttonTapped:(id) sender
375{
376 if (self.buttonCallback)
377 {
378 self.buttonCallback();
379 }
380
381 [self fadeMeOut];
382}
383
384@end