From 9b99b62980f8468a7ade97b5e217a65fb1519dd2 Mon Sep 17 00:00:00 2001 From: EddyVerbruggen Date: Sat, 25 Jan 2014 11:45:10 +0100 Subject: [PATCH] iOS imp work in progress --- plugin.xml | 6 +- src/ios/Toast+UIView.h | 51 ++++++ src/ios/Toast+UIView.m | 363 +++++++++++++++++++++++++++++++++++++++++ src/ios/Toast.m | 10 +- 4 files changed, 424 insertions(+), 6 deletions(-) create mode 100755 src/ios/Toast+UIView.h create mode 100755 src/ios/Toast+UIView.m diff --git a/plugin.xml b/plugin.xml index 296e9d8..da4ee66 100755 --- a/plugin.xml +++ b/plugin.xml @@ -30,10 +30,12 @@ - - + + + + diff --git a/src/ios/Toast+UIView.h b/src/ios/Toast+UIView.h new file mode 100755 index 0000000..652ef35 --- /dev/null +++ b/src/ios/Toast+UIView.h @@ -0,0 +1,51 @@ +/*************************************************************************** + +Toast+UIView.h +Toast +Version 2.2 + +Copyright (c) 2013 Charles Scalesse. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +***************************************************************************/ + + +#import + +@interface UIView (Toast) + +// each makeToast method creates a view and displays it as toast +- (void)makeToast:(NSString *)message; +- (void)makeToast:(NSString *)message duration:(NSTimeInterval)interval position:(id)position; +- (void)makeToast:(NSString *)message duration:(NSTimeInterval)interval position:(id)position image:(UIImage *)image; +- (void)makeToast:(NSString *)message duration:(NSTimeInterval)interval position:(id)position title:(NSString *)title; +- (void)makeToast:(NSString *)message duration:(NSTimeInterval)interval position:(id)position title:(NSString *)title image:(UIImage *)image; + +// displays toast with an activity spinner +- (void)makeToastActivity; +- (void)makeToastActivity:(id)position; +- (void)hideToastActivity; + +// the showToast methods display any view as toast +- (void)showToast:(UIView *)toast; +- (void)showToast:(UIView *)toast duration:(NSTimeInterval)interval position:(id)point; + +@end diff --git a/src/ios/Toast+UIView.m b/src/ios/Toast+UIView.m new file mode 100755 index 0000000..0c6b97e --- /dev/null +++ b/src/ios/Toast+UIView.m @@ -0,0 +1,363 @@ +// +// Toast+UIView.m +// Toast +// Version 2.2 +// +// Copyright 2013 Charles Scalesse. +// + +#import "Toast+UIView.h" +#import +#import + +/* + * CONFIGURE THESE VALUES TO ADJUST LOOK & FEEL, + * DISPLAY DURATION, ETC. + */ + +// general appearance +static const CGFloat CSToastMaxWidth = 0.8; // 80% of parent view width +static const CGFloat CSToastMaxHeight = 0.8; // 80% of parent view height +static const CGFloat CSToastHorizontalPadding = 10.0; +static const CGFloat CSToastVerticalPadding = 10.0; +static const CGFloat CSToastCornerRadius = 10.0; +static const CGFloat CSToastOpacity = 0.8; +static const CGFloat CSToastFontSize = 16.0; +static const CGFloat CSToastMaxTitleLines = 0; +static const CGFloat CSToastMaxMessageLines = 0; +static const NSTimeInterval CSToastFadeDuration = 0.2; + +// shadow appearance +static const CGFloat CSToastShadowOpacity = 0.8; +static const CGFloat CSToastShadowRadius = 6.0; +static const CGSize CSToastShadowOffset = { 4.0, 4.0 }; +static const BOOL CSToastDisplayShadow = YES; + +// display duration and position +static const NSString * CSToastDefaultPosition = @"bottom"; +static const NSTimeInterval CSToastDefaultDuration = 3.0; + +// image view size +static const CGFloat CSToastImageViewWidth = 80.0; +static const CGFloat CSToastImageViewHeight = 80.0; + +// activity +static const CGFloat CSToastActivityWidth = 100.0; +static const CGFloat CSToastActivityHeight = 100.0; +static const NSString * CSToastActivityDefaultPosition = @"center"; + +// interaction +static const BOOL CSToastHidesOnTap = YES; // excludes activity views + +// associative reference keys +static const NSString * CSToastTimerKey = @"CSToastTimerKey"; +static const NSString * CSToastActivityViewKey = @"CSToastActivityViewKey"; + +@interface UIView (ToastPrivate) + +- (void)hideToast:(UIView *)toast; +- (void)toastTimerDidFinish:(NSTimer *)timer; +- (void)handleToastTapped:(UITapGestureRecognizer *)recognizer; +- (CGPoint)centerPointForPosition:(id)position withToast:(UIView *)toast; +- (UIView *)viewForMessage:(NSString *)message title:(NSString *)title image:(UIImage *)image; +- (CGSize)sizeForString:(NSString *)string font:(UIFont *)font constrainedToSize:(CGSize)constrainedSize lineBreakMode:(NSLineBreakMode)lineBreakMode; + +@end + + +@implementation UIView (Toast) + +#pragma mark - Toast Methods + +- (void)makeToast:(NSString *)message { + [self makeToast:message duration:CSToastDefaultDuration position:CSToastDefaultPosition]; +} + +- (void)makeToast:(NSString *)message duration:(NSTimeInterval)duration position:(id)position { + UIView *toast = [self viewForMessage:message title:nil image:nil]; + [self showToast:toast duration:duration position:position]; +} + +- (void)makeToast:(NSString *)message duration:(NSTimeInterval)duration position:(id)position title:(NSString *)title { + UIView *toast = [self viewForMessage:message title:title image:nil]; + [self showToast:toast duration:duration position:position]; +} + +- (void)makeToast:(NSString *)message duration:(NSTimeInterval)duration position:(id)position image:(UIImage *)image { + UIView *toast = [self viewForMessage:message title:nil image:image]; + [self showToast:toast duration:duration position:position]; +} + +- (void)makeToast:(NSString *)message duration:(NSTimeInterval)duration position:(id)position title:(NSString *)title image:(UIImage *)image { + UIView *toast = [self viewForMessage:message title:title image:image]; + [self showToast:toast duration:duration position:position]; +} + +- (void)showToast:(UIView *)toast { + [self showToast:toast duration:CSToastDefaultDuration position:CSToastDefaultPosition]; +} + +- (void)showToast:(UIView *)toast duration:(NSTimeInterval)duration position:(id)point { + toast.center = [self centerPointForPosition:point withToast:toast]; + toast.alpha = 0.0; + + if (CSToastHidesOnTap) { + UITapGestureRecognizer *recognizer = [[UITapGestureRecognizer alloc] initWithTarget:toast action:@selector(handleToastTapped:)]; + [toast addGestureRecognizer:recognizer]; + toast.userInteractionEnabled = YES; + toast.exclusiveTouch = YES; + } + + [self addSubview:toast]; + + [UIView animateWithDuration:CSToastFadeDuration + delay:0.0 + options:(UIViewAnimationOptionCurveEaseOut | UIViewAnimationOptionAllowUserInteraction) + animations:^{ + toast.alpha = 1.0; + } completion:^(BOOL finished) { + NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:duration target:self selector:@selector(toastTimerDidFinish:) userInfo:toast repeats:NO]; + // associate the timer with the toast view + objc_setAssociatedObject (toast, &CSToastTimerKey, timer, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + }]; + +} + +- (void)hideToast:(UIView *)toast { + [UIView animateWithDuration:CSToastFadeDuration + delay:0.0 + options:(UIViewAnimationOptionCurveEaseIn | UIViewAnimationOptionBeginFromCurrentState) + animations:^{ + toast.alpha = 0.0; + } completion:^(BOOL finished) { + [toast removeFromSuperview]; + }]; +} + +#pragma mark - Events + +- (void)toastTimerDidFinish:(NSTimer *)timer { + [self hideToast:(UIView *)timer.userInfo]; +} + +- (void)handleToastTapped:(UITapGestureRecognizer *)recognizer { + NSTimer *timer = (NSTimer *)objc_getAssociatedObject(self, &CSToastTimerKey); + [timer invalidate]; + + [self hideToast:recognizer.view]; +} + +#pragma mark - Toast Activity Methods + +- (void)makeToastActivity { + [self makeToastActivity:CSToastActivityDefaultPosition]; +} + +- (void)makeToastActivity:(id)position { + // sanity + UIView *existingActivityView = (UIView *)objc_getAssociatedObject(self, &CSToastActivityViewKey); + if (existingActivityView != nil) return; + + UIView *activityView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, CSToastActivityWidth, CSToastActivityHeight)]; + activityView.center = [self centerPointForPosition:position withToast:activityView]; + activityView.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:CSToastOpacity]; + activityView.alpha = 0.0; + activityView.autoresizingMask = (UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin); + activityView.layer.cornerRadius = CSToastCornerRadius; + + if (CSToastDisplayShadow) { + activityView.layer.shadowColor = [UIColor blackColor].CGColor; + activityView.layer.shadowOpacity = CSToastShadowOpacity; + activityView.layer.shadowRadius = CSToastShadowRadius; + activityView.layer.shadowOffset = CSToastShadowOffset; + } + + UIActivityIndicatorView *activityIndicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge]; + activityIndicatorView.center = CGPointMake(activityView.bounds.size.width / 2, activityView.bounds.size.height / 2); + [activityView addSubview:activityIndicatorView]; + [activityIndicatorView startAnimating]; + + // associate the activity view with self + objc_setAssociatedObject (self, &CSToastActivityViewKey, activityView, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + + [self addSubview:activityView]; + + [UIView animateWithDuration:CSToastFadeDuration + delay:0.0 + options:UIViewAnimationOptionCurveEaseOut + animations:^{ + activityView.alpha = 1.0; + } completion:nil]; +} + +- (void)hideToastActivity { + UIView *existingActivityView = (UIView *)objc_getAssociatedObject(self, &CSToastActivityViewKey); + if (existingActivityView != nil) { + [UIView animateWithDuration:CSToastFadeDuration + delay:0.0 + options:(UIViewAnimationOptionCurveEaseIn | UIViewAnimationOptionBeginFromCurrentState) + animations:^{ + existingActivityView.alpha = 0.0; + } completion:^(BOOL finished) { + [existingActivityView removeFromSuperview]; + objc_setAssociatedObject (self, &CSToastActivityViewKey, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + }]; + } +} + +#pragma mark - Helpers + +- (CGPoint)centerPointForPosition:(id)point withToast:(UIView *)toast { + if([point isKindOfClass:[NSString class]]) { + // convert string literals @"top", @"bottom", @"center", or any point wrapped in an NSValue object into a CGPoint + if([point caseInsensitiveCompare:@"top"] == NSOrderedSame) { + return CGPointMake(self.bounds.size.width/2, (toast.frame.size.height / 2) + CSToastVerticalPadding); + } else if([point caseInsensitiveCompare:@"bottom"] == NSOrderedSame) { + return CGPointMake(self.bounds.size.width/2, (self.bounds.size.height - (toast.frame.size.height / 2)) - CSToastVerticalPadding); + } else if([point caseInsensitiveCompare:@"center"] == NSOrderedSame) { + return CGPointMake(self.bounds.size.width / 2, self.bounds.size.height / 2); + } + } else if ([point isKindOfClass:[NSValue class]]) { + return [point CGPointValue]; + } + + NSLog(@"Warning: Invalid position for toast."); + return [self centerPointForPosition:CSToastDefaultPosition withToast:toast]; +} + +- (CGSize)sizeForString:(NSString *)string font:(UIFont *)font constrainedToSize:(CGSize)constrainedSize lineBreakMode:(NSLineBreakMode)lineBreakMode { + if ([string respondsToSelector:@selector(boundingRectWithSize:options:attributes:context:)]) { + NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init]; + paragraphStyle.lineBreakMode = lineBreakMode; + NSDictionary *attributes = @{NSFontAttributeName:font, NSParagraphStyleAttributeName:paragraphStyle}; + CGRect boundingRect = [string boundingRectWithSize:constrainedSize options:NSStringDrawingUsesLineFragmentOrigin attributes:attributes context:nil]; + return CGSizeMake(ceilf(boundingRect.size.width), ceilf(boundingRect.size.height)); + } + + return [string sizeWithFont:font constrainedToSize:constrainedSize lineBreakMode:lineBreakMode]; +} + +- (UIView *)viewForMessage:(NSString *)message title:(NSString *)title image:(UIImage *)image { + // sanity + if((message == nil) && (title == nil) && (image == nil)) return nil; + + // dynamically build a toast view with any combination of message, title, & image. + UILabel *messageLabel = nil; + UILabel *titleLabel = nil; + UIImageView *imageView = nil; + + // create the parent view + UIView *wrapperView = [[UIView alloc] init]; + wrapperView.autoresizingMask = (UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin); + wrapperView.layer.cornerRadius = CSToastCornerRadius; + + if (CSToastDisplayShadow) { + wrapperView.layer.shadowColor = [UIColor blackColor].CGColor; + wrapperView.layer.shadowOpacity = CSToastShadowOpacity; + wrapperView.layer.shadowRadius = CSToastShadowRadius; + wrapperView.layer.shadowOffset = CSToastShadowOffset; + } + + wrapperView.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:CSToastOpacity]; + + if(image != nil) { + imageView = [[UIImageView alloc] initWithImage:image]; + imageView.contentMode = UIViewContentModeScaleAspectFit; + imageView.frame = CGRectMake(CSToastHorizontalPadding, CSToastVerticalPadding, CSToastImageViewWidth, CSToastImageViewHeight); + } + + CGFloat imageWidth, imageHeight, imageLeft; + + // the imageView frame values will be used to size & position the other views + if(imageView != nil) { + imageWidth = imageView.bounds.size.width; + imageHeight = imageView.bounds.size.height; + imageLeft = CSToastHorizontalPadding; + } else { + imageWidth = imageHeight = imageLeft = 0.0; + } + + if (title != nil) { + titleLabel = [[UILabel alloc] init]; + titleLabel.numberOfLines = CSToastMaxTitleLines; + titleLabel.font = [UIFont boldSystemFontOfSize:CSToastFontSize]; + titleLabel.textAlignment = NSTextAlignmentLeft; + titleLabel.lineBreakMode = NSLineBreakByWordWrapping; + titleLabel.textColor = [UIColor whiteColor]; + titleLabel.backgroundColor = [UIColor clearColor]; + titleLabel.alpha = 1.0; + titleLabel.text = title; + + // size the title label according to the length of the text + CGSize maxSizeTitle = CGSizeMake((self.bounds.size.width * CSToastMaxWidth) - imageWidth, self.bounds.size.height * CSToastMaxHeight); + CGSize expectedSizeTitle = [self sizeForString:title font:titleLabel.font constrainedToSize:maxSizeTitle lineBreakMode:titleLabel.lineBreakMode]; + titleLabel.frame = CGRectMake(0.0, 0.0, expectedSizeTitle.width, expectedSizeTitle.height); + } + + if (message != nil) { + messageLabel = [[UILabel alloc] init]; + messageLabel.numberOfLines = CSToastMaxMessageLines; + messageLabel.font = [UIFont systemFontOfSize:CSToastFontSize]; + messageLabel.lineBreakMode = NSLineBreakByWordWrapping; + messageLabel.textColor = [UIColor whiteColor]; + messageLabel.backgroundColor = [UIColor clearColor]; + messageLabel.alpha = 1.0; + messageLabel.text = message; + + // size the message label according to the length of the text + CGSize maxSizeMessage = CGSizeMake((self.bounds.size.width * CSToastMaxWidth) - imageWidth, self.bounds.size.height * CSToastMaxHeight); + CGSize expectedSizeMessage = [self sizeForString:message font:messageLabel.font constrainedToSize:maxSizeMessage lineBreakMode:messageLabel.lineBreakMode]; + messageLabel.frame = CGRectMake(0.0, 0.0, expectedSizeMessage.width, expectedSizeMessage.height); + } + + // titleLabel frame values + CGFloat titleWidth, titleHeight, titleTop, titleLeft; + + if(titleLabel != nil) { + titleWidth = titleLabel.bounds.size.width; + titleHeight = titleLabel.bounds.size.height; + titleTop = CSToastVerticalPadding; + titleLeft = imageLeft + imageWidth + CSToastHorizontalPadding; + } else { + titleWidth = titleHeight = titleTop = titleLeft = 0.0; + } + + // messageLabel frame values + CGFloat messageWidth, messageHeight, messageLeft, messageTop; + + if(messageLabel != nil) { + messageWidth = messageLabel.bounds.size.width; + messageHeight = messageLabel.bounds.size.height; + messageLeft = imageLeft + imageWidth + CSToastHorizontalPadding; + messageTop = titleTop + titleHeight + CSToastVerticalPadding; + } else { + messageWidth = messageHeight = messageLeft = messageTop = 0.0; + } + + CGFloat longerWidth = MAX(titleWidth, messageWidth); + CGFloat longerLeft = MAX(titleLeft, messageLeft); + + // wrapper width uses the longerWidth or the image width, whatever is larger. same logic applies to the wrapper height + CGFloat wrapperWidth = MAX((imageWidth + (CSToastHorizontalPadding * 2)), (longerLeft + longerWidth + CSToastHorizontalPadding)); + CGFloat wrapperHeight = MAX((messageTop + messageHeight + CSToastVerticalPadding), (imageHeight + (CSToastVerticalPadding * 2))); + + wrapperView.frame = CGRectMake(0.0, 0.0, wrapperWidth, wrapperHeight); + + if(titleLabel != nil) { + titleLabel.frame = CGRectMake(titleLeft, titleTop, titleWidth, titleHeight); + [wrapperView addSubview:titleLabel]; + } + + if(messageLabel != nil) { + messageLabel.frame = CGRectMake(messageLeft, messageTop, messageWidth, messageHeight); + [wrapperView addSubview:messageLabel]; + } + + if(imageView != nil) { + [wrapperView addSubview:imageView]; + } + + return wrapperView; +} + +@end diff --git a/src/ios/Toast.m b/src/ios/Toast.m index 7ded471..8cd96ad 100755 --- a/src/ios/Toast.m +++ b/src/ios/Toast.m @@ -1,6 +1,6 @@ #import "Toast.h" -#import // TODO: required? -#import "lib/iToast.h" +#import "Toast+UIView.h" +#import @implementation Toast @@ -15,8 +15,10 @@ // TODO pass in NSInteger drTime = iToastDurationShort; - [[[[iToast makeText:NSLocalizedString(message, @"")] - setGravity:grv offsetLeft:0 offsetTop:0] setDuration:drTime] show]; + [self.view makeToast:@"This is a piece of toast."]; + +// [[[[iToast makeText:NSLocalizedString(message, @"")] +// setGravity:grv offsetLeft:0 offsetTop:0] setDuration:drTime] show]; }