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