New file |
| | |
| | | // |
| | | // UIView+Toast.m |
| | | // Toast |
| | | // |
| | | // Copyright (c) 2011-2017 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 "UIView+Toast.h" |
| | | #import <QuartzCore/QuartzCore.h> |
| | | #import <objc/runtime.h> |
| | | |
| | | // Positions |
| | | NSString * CSToastPositionTop = @"CSToastPositionTop"; |
| | | NSString * CSToastPositionCenter = @"CSToastPositionCenter"; |
| | | NSString * CSToastPositionBottom = @"CSToastPositionBottom"; |
| | | |
| | | // Keys for values associated with toast views |
| | | static const NSString * CSToastTimerKey = @"CSToastTimerKey"; |
| | | static const NSString * CSToastDurationKey = @"CSToastDurationKey"; |
| | | static const NSString * CSToastPositionKey = @"CSToastPositionKey"; |
| | | static const NSString * CSToastCompletionKey = @"CSToastCompletionKey"; |
| | | |
| | | // Keys for values associated with self |
| | | static const NSString * CSToastActiveKey = @"CSToastActiveKey"; |
| | | static const NSString * CSToastActivityViewKey = @"CSToastActivityViewKey"; |
| | | static const NSString * CSToastQueueKey = @"CSToastQueueKey"; |
| | | |
| | | @interface UIView (ToastPrivate) |
| | | |
| | | /** |
| | | These private methods are being prefixed with "cs_" to reduce the likelihood of non-obvious |
| | | naming conflicts with other UIView methods. |
| | | |
| | | @discussion Should the public API also use the cs_ prefix? Technically it should, but it |
| | | results in code that is less legible. The current public method names seem unlikely to cause |
| | | conflicts so I think we should favor the cleaner API for now. |
| | | */ |
| | | - (void)cs_showToast:(UIView *)toast duration:(NSTimeInterval)duration position:(id)position; |
| | | - (void)cs_hideToast:(UIView *)toast; |
| | | - (void)cs_hideToast:(UIView *)toast fromTap:(BOOL)fromTap; |
| | | - (void)cs_toastTimerDidFinish:(NSTimer *)timer; |
| | | - (void)cs_handleToastTapped:(UITapGestureRecognizer *)recognizer; |
| | | - (CGPoint)cs_centerPointForPosition:(id)position withToast:(UIView *)toast; |
| | | - (NSMutableArray *)cs_toastQueue; |
| | | |
| | | @end |
| | | |
| | | @implementation UIView (Toast) |
| | | |
| | | #pragma mark - Make Toast Methods |
| | | |
| | | - (void)makeToast:(NSString *)message { |
| | | [self makeToast:message duration:[CSToastManager defaultDuration] position:[CSToastManager defaultPosition] style:nil]; |
| | | } |
| | | |
| | | - (void)makeToast:(NSString *)message duration:(NSTimeInterval)duration position:(id)position { |
| | | [self makeToast:message duration:duration position:position style:nil]; |
| | | } |
| | | |
| | | - (void)makeToast:(NSString *)message duration:(NSTimeInterval)duration position:(id)position style:(CSToastStyle *)style { |
| | | UIView *toast = [self toastViewForMessage:message title:nil image:nil style:style]; |
| | | [self showToast:toast duration:duration position:position completion:nil]; |
| | | } |
| | | |
| | | - (void)makeToast:(NSString *)message duration:(NSTimeInterval)duration position:(id)position title:(NSString *)title image:(UIImage *)image style:(CSToastStyle *)style completion:(void(^)(BOOL didTap))completion { |
| | | UIView *toast = [self toastViewForMessage:message title:title image:image style:style]; |
| | | [self showToast:toast duration:duration position:position completion:completion]; |
| | | } |
| | | |
| | | #pragma mark - Show Toast Methods |
| | | |
| | | - (void)showToast:(UIView *)toast { |
| | | [self showToast:toast duration:[CSToastManager defaultDuration] position:[CSToastManager defaultPosition] completion:nil]; |
| | | } |
| | | |
| | | - (void)showToast:(UIView *)toast duration:(NSTimeInterval)duration position:(id)position completion:(void(^)(BOOL didTap))completion { |
| | | // sanity |
| | | if (toast == nil) return; |
| | | |
| | | // store the completion block on the toast view |
| | | objc_setAssociatedObject(toast, &CSToastCompletionKey, completion, OBJC_ASSOCIATION_RETAIN_NONATOMIC); |
| | | |
| | | if ([CSToastManager isQueueEnabled] && [self.cs_activeToasts count] > 0) { |
| | | // we're about to queue this toast view so we need to store the duration and position as well |
| | | objc_setAssociatedObject(toast, &CSToastDurationKey, @(duration), OBJC_ASSOCIATION_RETAIN_NONATOMIC); |
| | | objc_setAssociatedObject(toast, &CSToastPositionKey, position, OBJC_ASSOCIATION_RETAIN_NONATOMIC); |
| | | |
| | | // enqueue |
| | | [self.cs_toastQueue addObject:toast]; |
| | | } else { |
| | | // present |
| | | [self cs_showToast:toast duration:duration position:position]; |
| | | } |
| | | } |
| | | |
| | | #pragma mark - Hide Toast Methods |
| | | |
| | | - (void)hideToast { |
| | | [self hideToast:[[self cs_activeToasts] firstObject]]; |
| | | } |
| | | |
| | | - (void)hideToast:(UIView *)toast { |
| | | // sanity |
| | | if (!toast || ![[self cs_activeToasts] containsObject:toast]) return; |
| | | |
| | | [self cs_hideToast:toast]; |
| | | } |
| | | |
| | | - (void)hideAllToasts { |
| | | [self hideAllToasts:NO clearQueue:YES]; |
| | | } |
| | | |
| | | - (void)hideAllToasts:(BOOL)includeActivity clearQueue:(BOOL)clearQueue { |
| | | if (clearQueue) { |
| | | [self clearToastQueue]; |
| | | } |
| | | |
| | | for (UIView *toast in [self cs_activeToasts]) { |
| | | [self hideToast:toast]; |
| | | } |
| | | |
| | | if (includeActivity) { |
| | | [self hideToastActivity]; |
| | | } |
| | | } |
| | | |
| | | - (void)clearToastQueue { |
| | | [[self cs_toastQueue] removeAllObjects]; |
| | | } |
| | | |
| | | #pragma mark - Private Show/Hide Methods |
| | | |
| | | - (void)cs_showToast:(UIView *)toast duration:(NSTimeInterval)duration position:(id)position { |
| | | toast.center = [self cs_centerPointForPosition:position withToast:toast]; |
| | | toast.alpha = 0.0; |
| | | |
| | | if ([CSToastManager isTapToDismissEnabled]) { |
| | | UITapGestureRecognizer *recognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(cs_handleToastTapped:)]; |
| | | [toast addGestureRecognizer:recognizer]; |
| | | toast.userInteractionEnabled = YES; |
| | | toast.exclusiveTouch = YES; |
| | | } |
| | | |
| | | [[self cs_activeToasts] addObject:toast]; |
| | | |
| | | [self addSubview:toast]; |
| | | |
| | | [UIView animateWithDuration:[[CSToastManager sharedStyle] fadeDuration] |
| | | delay:0.0 |
| | | options:(UIViewAnimationOptionCurveEaseOut | UIViewAnimationOptionAllowUserInteraction) |
| | | animations:^{ |
| | | toast.alpha = 1.0; |
| | | } completion:^(BOOL finished) { |
| | | NSTimer *timer = [NSTimer timerWithTimeInterval:duration target:self selector:@selector(cs_toastTimerDidFinish:) userInfo:toast repeats:NO]; |
| | | [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; |
| | | objc_setAssociatedObject(toast, &CSToastTimerKey, timer, OBJC_ASSOCIATION_RETAIN_NONATOMIC); |
| | | }]; |
| | | } |
| | | |
| | | - (void)cs_hideToast:(UIView *)toast { |
| | | [self cs_hideToast:toast fromTap:NO]; |
| | | } |
| | | |
| | | - (void)cs_hideToast:(UIView *)toast fromTap:(BOOL)fromTap { |
| | | NSTimer *timer = (NSTimer *)objc_getAssociatedObject(toast, &CSToastTimerKey); |
| | | [timer invalidate]; |
| | | |
| | | [UIView animateWithDuration:[[CSToastManager sharedStyle] fadeDuration] |
| | | delay:0.0 |
| | | options:(UIViewAnimationOptionCurveEaseIn | UIViewAnimationOptionBeginFromCurrentState) |
| | | animations:^{ |
| | | toast.alpha = 0.0; |
| | | } completion:^(BOOL finished) { |
| | | [toast removeFromSuperview]; |
| | | |
| | | // remove |
| | | [[self cs_activeToasts] removeObject:toast]; |
| | | |
| | | // execute the completion block, if necessary |
| | | void (^completion)(BOOL didTap) = objc_getAssociatedObject(toast, &CSToastCompletionKey); |
| | | if (completion) { |
| | | completion(fromTap); |
| | | } |
| | | |
| | | if ([self.cs_toastQueue count] > 0) { |
| | | // dequeue |
| | | UIView *nextToast = [[self cs_toastQueue] firstObject]; |
| | | [[self cs_toastQueue] removeObjectAtIndex:0]; |
| | | |
| | | // present the next toast |
| | | NSTimeInterval duration = [objc_getAssociatedObject(nextToast, &CSToastDurationKey) doubleValue]; |
| | | id position = objc_getAssociatedObject(nextToast, &CSToastPositionKey); |
| | | [self cs_showToast:nextToast duration:duration position:position]; |
| | | } |
| | | }]; |
| | | } |
| | | |
| | | #pragma mark - View Construction |
| | | |
| | | - (UIView *)toastViewForMessage:(NSString *)message title:(NSString *)title image:(UIImage *)image style:(CSToastStyle *)style { |
| | | // sanity |
| | | if (message == nil && title == nil && image == nil) return nil; |
| | | |
| | | // default to the shared style |
| | | if (style == nil) { |
| | | style = [CSToastManager sharedStyle]; |
| | | } |
| | | |
| | | // dynamically build a toast view with any combination of message, title, & image |
| | | UILabel *messageLabel = nil; |
| | | UILabel *titleLabel = nil; |
| | | UIImageView *imageView = nil; |
| | | |
| | | UIView *wrapperView = [[UIView alloc] init]; |
| | | wrapperView.autoresizingMask = (UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin); |
| | | wrapperView.layer.cornerRadius = style.cornerRadius; |
| | | |
| | | if (style.displayShadow) { |
| | | wrapperView.layer.shadowColor = style.shadowColor.CGColor; |
| | | wrapperView.layer.shadowOpacity = style.shadowOpacity; |
| | | wrapperView.layer.shadowRadius = style.shadowRadius; |
| | | wrapperView.layer.shadowOffset = style.shadowOffset; |
| | | } |
| | | |
| | | wrapperView.backgroundColor = style.backgroundColor; |
| | | |
| | | if(image != nil) { |
| | | imageView = [[UIImageView alloc] initWithImage:image]; |
| | | imageView.contentMode = UIViewContentModeScaleAspectFit; |
| | | imageView.frame = CGRectMake(style.horizontalPadding, style.verticalPadding, style.imageSize.width, style.imageSize.height); |
| | | } |
| | | |
| | | CGRect imageRect = CGRectZero; |
| | | |
| | | if(imageView != nil) { |
| | | imageRect.origin.x = style.horizontalPadding; |
| | | imageRect.origin.y = style.verticalPadding; |
| | | imageRect.size.width = imageView.bounds.size.width; |
| | | imageRect.size.height = imageView.bounds.size.height; |
| | | } |
| | | |
| | | if (title != nil) { |
| | | titleLabel = [[UILabel alloc] init]; |
| | | titleLabel.numberOfLines = style.titleNumberOfLines; |
| | | titleLabel.font = style.titleFont; |
| | | titleLabel.textAlignment = style.titleAlignment; |
| | | titleLabel.lineBreakMode = NSLineBreakByTruncatingTail; |
| | | titleLabel.textColor = style.titleColor; |
| | | 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 * style.maxWidthPercentage) - imageRect.size.width, self.bounds.size.height * style.maxHeightPercentage); |
| | | CGSize expectedSizeTitle = [titleLabel sizeThatFits:maxSizeTitle]; |
| | | // UILabel can return a size larger than the max size when the number of lines is 1 |
| | | expectedSizeTitle = CGSizeMake(MIN(maxSizeTitle.width, expectedSizeTitle.width), MIN(maxSizeTitle.height, expectedSizeTitle.height)); |
| | | titleLabel.frame = CGRectMake(0.0, 0.0, expectedSizeTitle.width, expectedSizeTitle.height); |
| | | } |
| | | |
| | | if (message != nil) { |
| | | messageLabel = [[UILabel alloc] init]; |
| | | messageLabel.numberOfLines = style.messageNumberOfLines; |
| | | messageLabel.font = style.messageFont; |
| | | messageLabel.textAlignment = style.messageAlignment; |
| | | messageLabel.lineBreakMode = NSLineBreakByTruncatingTail; |
| | | messageLabel.textColor = style.messageColor; |
| | | messageLabel.backgroundColor = [UIColor clearColor]; |
| | | messageLabel.alpha = 1.0; |
| | | messageLabel.text = message; |
| | | |
| | | CGSize maxSizeMessage = CGSizeMake((self.bounds.size.width * style.maxWidthPercentage) - imageRect.size.width, self.bounds.size.height * style.maxHeightPercentage); |
| | | CGSize expectedSizeMessage = [messageLabel sizeThatFits:maxSizeMessage]; |
| | | // UILabel can return a size larger than the max size when the number of lines is 1 |
| | | expectedSizeMessage = CGSizeMake(MIN(maxSizeMessage.width, expectedSizeMessage.width), MIN(maxSizeMessage.height, expectedSizeMessage.height)); |
| | | messageLabel.frame = CGRectMake(0.0, 0.0, expectedSizeMessage.width, expectedSizeMessage.height); |
| | | } |
| | | |
| | | CGRect titleRect = CGRectZero; |
| | | |
| | | if(titleLabel != nil) { |
| | | titleRect.origin.x = imageRect.origin.x + imageRect.size.width + style.horizontalPadding; |
| | | titleRect.origin.y = style.verticalPadding; |
| | | titleRect.size.width = titleLabel.bounds.size.width; |
| | | titleRect.size.height = titleLabel.bounds.size.height; |
| | | } |
| | | |
| | | CGRect messageRect = CGRectZero; |
| | | |
| | | if(messageLabel != nil) { |
| | | messageRect.origin.x = imageRect.origin.x + imageRect.size.width + style.horizontalPadding; |
| | | messageRect.origin.y = titleRect.origin.y + titleRect.size.height + style.verticalPadding; |
| | | messageRect.size.width = messageLabel.bounds.size.width; |
| | | messageRect.size.height = messageLabel.bounds.size.height; |
| | | } |
| | | |
| | | CGFloat longerWidth = MAX(titleRect.size.width, messageRect.size.width); |
| | | CGFloat longerX = MAX(titleRect.origin.x, messageRect.origin.x); |
| | | |
| | | // Wrapper width uses the longerWidth or the image width, whatever is larger. Same logic applies to the wrapper height. |
| | | CGFloat wrapperWidth = MAX((imageRect.size.width + (style.horizontalPadding * 2.0)), (longerX + longerWidth + style.horizontalPadding)); |
| | | CGFloat wrapperHeight = MAX((messageRect.origin.y + messageRect.size.height + style.verticalPadding), (imageRect.size.height + (style.verticalPadding * 2.0))); |
| | | |
| | | wrapperView.frame = CGRectMake(0.0, 0.0, wrapperWidth, wrapperHeight); |
| | | |
| | | if(titleLabel != nil) { |
| | | titleLabel.frame = titleRect; |
| | | [wrapperView addSubview:titleLabel]; |
| | | } |
| | | |
| | | if(messageLabel != nil) { |
| | | messageLabel.frame = messageRect; |
| | | [wrapperView addSubview:messageLabel]; |
| | | } |
| | | |
| | | if(imageView != nil) { |
| | | [wrapperView addSubview:imageView]; |
| | | } |
| | | |
| | | return wrapperView; |
| | | } |
| | | |
| | | #pragma mark - Storage |
| | | |
| | | - (NSMutableArray *)cs_activeToasts { |
| | | NSMutableArray *cs_activeToasts = objc_getAssociatedObject(self, &CSToastActiveKey); |
| | | if (cs_activeToasts == nil) { |
| | | cs_activeToasts = [[NSMutableArray alloc] init]; |
| | | objc_setAssociatedObject(self, &CSToastActiveKey, cs_activeToasts, OBJC_ASSOCIATION_RETAIN_NONATOMIC); |
| | | } |
| | | return cs_activeToasts; |
| | | } |
| | | |
| | | - (NSMutableArray *)cs_toastQueue { |
| | | NSMutableArray *cs_toastQueue = objc_getAssociatedObject(self, &CSToastQueueKey); |
| | | if (cs_toastQueue == nil) { |
| | | cs_toastQueue = [[NSMutableArray alloc] init]; |
| | | objc_setAssociatedObject(self, &CSToastQueueKey, cs_toastQueue, OBJC_ASSOCIATION_RETAIN_NONATOMIC); |
| | | } |
| | | return cs_toastQueue; |
| | | } |
| | | |
| | | #pragma mark - Events |
| | | |
| | | - (void)cs_toastTimerDidFinish:(NSTimer *)timer { |
| | | [self cs_hideToast:(UIView *)timer.userInfo]; |
| | | } |
| | | |
| | | - (void)cs_handleToastTapped:(UITapGestureRecognizer *)recognizer { |
| | | UIView *toast = recognizer.view; |
| | | NSTimer *timer = (NSTimer *)objc_getAssociatedObject(toast, &CSToastTimerKey); |
| | | [timer invalidate]; |
| | | |
| | | [self cs_hideToast:toast fromTap:YES]; |
| | | } |
| | | |
| | | #pragma mark - Activity Methods |
| | | |
| | | - (void)makeToastActivity:(id)position { |
| | | // sanity |
| | | UIView *existingActivityView = (UIView *)objc_getAssociatedObject(self, &CSToastActivityViewKey); |
| | | if (existingActivityView != nil) return; |
| | | |
| | | CSToastStyle *style = [CSToastManager sharedStyle]; |
| | | |
| | | UIView *activityView = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, style.activitySize.width, style.activitySize.height)]; |
| | | activityView.center = [self cs_centerPointForPosition:position withToast:activityView]; |
| | | activityView.backgroundColor = style.backgroundColor; |
| | | activityView.alpha = 0.0; |
| | | activityView.autoresizingMask = (UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin); |
| | | activityView.layer.cornerRadius = style.cornerRadius; |
| | | |
| | | if (style.displayShadow) { |
| | | activityView.layer.shadowColor = style.shadowColor.CGColor; |
| | | activityView.layer.shadowOpacity = style.shadowOpacity; |
| | | activityView.layer.shadowRadius = style.shadowRadius; |
| | | activityView.layer.shadowOffset = style.shadowOffset; |
| | | } |
| | | |
| | | 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:style.fadeDuration |
| | | 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:[[CSToastManager sharedStyle] fadeDuration] |
| | | 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)cs_centerPointForPosition:(id)point withToast:(UIView *)toast { |
| | | CSToastStyle *style = [CSToastManager sharedStyle]; |
| | | |
| | | UIEdgeInsets safeInsets = UIEdgeInsetsZero; |
| | | if (@available(iOS 11.0, *)) { |
| | | safeInsets = self.safeAreaInsets; |
| | | } |
| | | |
| | | CGFloat topPadding = style.verticalPadding + safeInsets.top; |
| | | CGFloat bottomPadding = style.verticalPadding + safeInsets.bottom; |
| | | |
| | | if([point isKindOfClass:[NSString class]]) { |
| | | if([point caseInsensitiveCompare:CSToastPositionTop] == NSOrderedSame) { |
| | | return CGPointMake(self.bounds.size.width / 2.0, (toast.frame.size.height / 2.0) + topPadding); |
| | | } else if([point caseInsensitiveCompare:CSToastPositionCenter] == NSOrderedSame) { |
| | | return CGPointMake(self.bounds.size.width / 2.0, self.bounds.size.height / 2.0); |
| | | } |
| | | } else if ([point isKindOfClass:[NSValue class]]) { |
| | | return [point CGPointValue]; |
| | | } |
| | | |
| | | // default to bottom |
| | | return CGPointMake(self.bounds.size.width / 2.0, (self.bounds.size.height - (toast.frame.size.height / 2.0)) - bottomPadding); |
| | | } |
| | | |
| | | @end |
| | | |
| | | @implementation CSToastStyle |
| | | |
| | | #pragma mark - Constructors |
| | | |
| | | - (instancetype)initWithDefaultStyle { |
| | | self = [super init]; |
| | | if (self) { |
| | | self.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.8]; |
| | | self.titleColor = [UIColor whiteColor]; |
| | | self.messageColor = [UIColor whiteColor]; |
| | | self.maxWidthPercentage = 0.8; |
| | | self.maxHeightPercentage = 0.8; |
| | | self.horizontalPadding = 10.0; |
| | | self.verticalPadding = 10.0; |
| | | self.cornerRadius = 10.0; |
| | | self.titleFont = [UIFont boldSystemFontOfSize:16.0]; |
| | | self.messageFont = [UIFont systemFontOfSize:16.0]; |
| | | self.titleAlignment = NSTextAlignmentLeft; |
| | | self.messageAlignment = NSTextAlignmentLeft; |
| | | self.titleNumberOfLines = 0; |
| | | self.messageNumberOfLines = 0; |
| | | self.displayShadow = NO; |
| | | self.shadowOpacity = 0.8; |
| | | self.shadowRadius = 6.0; |
| | | self.shadowOffset = CGSizeMake(4.0, 4.0); |
| | | self.imageSize = CGSizeMake(80.0, 80.0); |
| | | self.activitySize = CGSizeMake(100.0, 100.0); |
| | | self.fadeDuration = 0.2; |
| | | } |
| | | return self; |
| | | } |
| | | |
| | | - (void)setMaxWidthPercentage:(CGFloat)maxWidthPercentage { |
| | | _maxWidthPercentage = MAX(MIN(maxWidthPercentage, 1.0), 0.0); |
| | | } |
| | | |
| | | - (void)setMaxHeightPercentage:(CGFloat)maxHeightPercentage { |
| | | _maxHeightPercentage = MAX(MIN(maxHeightPercentage, 1.0), 0.0); |
| | | } |
| | | |
| | | - (instancetype)init NS_UNAVAILABLE { |
| | | return nil; |
| | | } |
| | | |
| | | @end |
| | | |
| | | @interface CSToastManager () |
| | | |
| | | @property (strong, nonatomic) CSToastStyle *sharedStyle; |
| | | @property (assign, nonatomic, getter=isTapToDismissEnabled) BOOL tapToDismissEnabled; |
| | | @property (assign, nonatomic, getter=isQueueEnabled) BOOL queueEnabled; |
| | | @property (assign, nonatomic) NSTimeInterval defaultDuration; |
| | | @property (strong, nonatomic) id defaultPosition; |
| | | |
| | | @end |
| | | |
| | | @implementation CSToastManager |
| | | |
| | | #pragma mark - Constructors |
| | | |
| | | + (instancetype)sharedManager { |
| | | static CSToastManager *_sharedManager = nil; |
| | | static dispatch_once_t oncePredicate; |
| | | dispatch_once(&oncePredicate, ^{ |
| | | _sharedManager = [[self alloc] init]; |
| | | }); |
| | | |
| | | return _sharedManager; |
| | | } |
| | | |
| | | - (instancetype)init { |
| | | self = [super init]; |
| | | if (self) { |
| | | self.sharedStyle = [[CSToastStyle alloc] initWithDefaultStyle]; |
| | | self.tapToDismissEnabled = YES; |
| | | self.queueEnabled = NO; |
| | | self.defaultDuration = 3.0; |
| | | self.defaultPosition = CSToastPositionBottom; |
| | | } |
| | | return self; |
| | | } |
| | | |
| | | #pragma mark - Singleton Methods |
| | | |
| | | + (void)setSharedStyle:(CSToastStyle *)sharedStyle { |
| | | [[self sharedManager] setSharedStyle:sharedStyle]; |
| | | } |
| | | |
| | | + (CSToastStyle *)sharedStyle { |
| | | return [[self sharedManager] sharedStyle]; |
| | | } |
| | | |
| | | + (void)setTapToDismissEnabled:(BOOL)tapToDismissEnabled { |
| | | [[self sharedManager] setTapToDismissEnabled:tapToDismissEnabled]; |
| | | } |
| | | |
| | | + (BOOL)isTapToDismissEnabled { |
| | | return [[self sharedManager] isTapToDismissEnabled]; |
| | | } |
| | | |
| | | + (void)setQueueEnabled:(BOOL)queueEnabled { |
| | | [[self sharedManager] setQueueEnabled:queueEnabled]; |
| | | } |
| | | |
| | | + (BOOL)isQueueEnabled { |
| | | return [[self sharedManager] isQueueEnabled]; |
| | | } |
| | | |
| | | + (void)setDefaultDuration:(NSTimeInterval)duration { |
| | | [[self sharedManager] setDefaultDuration:duration]; |
| | | } |
| | | |
| | | + (NSTimeInterval)defaultDuration { |
| | | return [[self sharedManager] defaultDuration]; |
| | | } |
| | | |
| | | + (void)setDefaultPosition:(id)position { |
| | | if ([position isKindOfClass:[NSString class]] || [position isKindOfClass:[NSValue class]]) { |
| | | [[self sharedManager] setDefaultPosition:position]; |
| | | } |
| | | } |
| | | |
| | | + (id)defaultPosition { |
| | | return [[self sharedManager] defaultPosition]; |
| | | } |
| | | |
| | | @end |