JToast.m
//
// Free. No warranty. Do what you want with it.
// Created by Jakar some time in 2014
//
#import "JToast.h"
//See https://github.com/JakarCo/ios-util for KeyboardStateListener
#import "KeyboardStateListener.h"
//See https://github.com/jivadevoe/NSTimer-Blocks for NSTimer+Blocks
#import "NSTimer+Blocks.h"
#import "JToastQueue.h"
@interface JToast(){
}
@property (nonatomic, strong) UIView *toast;
@property (nonatomic, strong) UILabel *label;
@property (nonatomic, weak) UIView *parent;
//@property (nonatomic, readonly) NSInteger duration;
@property (nonatomic, strong, readonly) NSString *message;
@property (nonatomic, readonly) ToastPosition position;
@end
@implementation JToast
const CGFloat TOAST_TRANSITION_LENGTH = 0.5f; //for use with [UIView animateWithDuration]
const NSInteger TOAST_DURATION_SHORT = 2;
const NSInteger TOAST_DURATION_MEDIUM = 3;
const NSInteger TOAST_DURATION_LONG = 4;
const CGFloat MIN_DIFF_FOR_ANIM_LENGTH = 0.1f;
const CGFloat TOAST_ALPHA_DEFAULT = 0.90f;
-(void) tapped:(UIGestureRecognizer *)g{
[JToastQueue removeToast:self ifDisplayed:YES];
}
-(void) setDefaults{
_toast = [[UIView alloc] init];
UITapGestureRecognizer *g = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapped:)];
[_toast addGestureRecognizer:g];
_label = [[UILabel alloc] init];
_toast.backgroundColor = [UIColor blackColor];
_toast.layer.cornerRadius = 5;
_toast.layer.borderColor = [UIColor blackColor].CGColor;
_toast.layer.borderWidth = 1;
_toast.layer.shadowColor = [UIColor blackColor].CGColor;
_toast.layer.shadowRadius = 5;
_toast.layer.shadowOpacity = 1.0;
_toast.layer.shadowOffset = CGSizeMake(0, 2);
_label.numberOfLines = 0;
_label.textAlignment = NSTextAlignmentCenter;
_label.lineBreakMode = NSLineBreakByWordWrapping;
UIFont *font = [UIFont systemFontOfSize:[UIFont smallSystemFontSize]];
_label.textColor = [UIColor whiteColor];
_label.font = font;
}
-(id)initWithTopWindow{
if (self = [super init]){
[self setDefaults];
_parent = [[[[UIApplication sharedApplication] keyWindow] subviews] lastObject];
if (_parent.isHidden){
_parent = [[[UIApplication sharedApplication].windows sortedArrayUsingComparator:^NSComparisonResult(UIWindow *win1, UIWindow *win2) {
return win1.windowLevel - win2.windowLevel;
}] lastObject];
}
}
return self;
}
-(id)initInTopViewFromChild:(UIViewController *)child{ //this will take a child view and get it's topmost parent
if (self=[super init]){
UIViewController *v = child;
while (v.parentViewController!=nil){
v = v.parentViewController;
}
_parent = v.view;
[self setDefaults];
}
return self;
}
-(id)initInParentView:(UIView *)parent{
if (self=[super init]){
_parent = parent;
[self setDefaults];
}
return self;
}
-(JToast *)toastWithMessage:(NSString *)message forToastDuration:(ToastDuration)duration atPosition:(ToastPosition)position{
CGFloat animLength;
CGFloat multiplier = 1.0;
switch (duration) {
case ToastDurationCalculatedLong:
multiplier = 1.5;
case ToastDurationCalculated:
animLength = ((message.length/6)+1)/(200/60); //6 = avg word length of 5 + 1 space :: +1 is because there will be 1 more word than number of spaces :: 200/60 is words per second
animLength = animLength*multiplier; //Add 50% to the length so reading is not rushed
animLength += TOAST_TRANSITION_LENGTH; //add the transition length since it can't be read very well while it's transitioning.
if (animLength<2.0)animLength=TOAST_DURATION_SHORT;
break;
case ToastDurationShort:
animLength = TOAST_DURATION_SHORT;
break;
case ToastDurationMedium:
animLength = TOAST_DURATION_MEDIUM;
break;
case ToastDurationLong:
animLength = TOAST_DURATION_LONG;
break;
}
return [self toastWithMessage:message forFloatDuration:animLength atPosition:position];
}
-(JToast *)toastWithMessage:(NSString *)message forFloatDuration:(CGFloat)duration atPosition:(ToastPosition)position{
_message = message;
_position = position;
_animLength = duration;
return self;
}
-(void)showSelf{
[_parent addSubview:_toast];
_toast.alpha = 0.0f;
_label.text = _message;
[_toast addSubview:_label];
// CGRect parentFrame = [JUIView frameConsideringOrientation:_parent];
CGRect parentFrame = _parent.bounds;
CGSize size = [JUIView sizeFromString:_message usingFont:_label.font withMaxWidth:(parentFrame.size.width-parentFrame.size.width*0.05)];
_label.frame = CGRectMake(0, 0, size.width, size.height);
// CGRect labelFrame = _label.frame; this was here to see values debugging
_toast.frame = CGRectMake(0, 0, size.width+(size.height*0.2), size.height+(size.height*0.2));
[JUIView centerView:_label withinView:_toast withPositioning:CenterPositionInBounds];
[JUIView centerView:_toast withinView:_parent withPositioning:CenterPositionHorizontally];//I will always center horizontally, then I will position based on ToastPosition
CGFloat toastVerticalBound = (parentFrame.size.height-[KeyboardStateListener sharedInstance].keyboardSize.height)-(_toast.frame.size.height/2);
CGFloat toastVerticalCenter;
switch (_position){
case ToastPositionBottom:
toastVerticalCenter = (toastVerticalBound-(toastVerticalBound/3));
break;
case ToastPositionCenter:
toastVerticalCenter = (toastVerticalBound/2);
break;
case ToastPositionTop:
toastVerticalCenter = (toastVerticalBound/3);
break;
}
_toast.center = CGPointMake(_toast.center.x, toastVerticalCenter);
[_parent bringSubviewToFront:_toast];
[UIView animateWithDuration:TOAST_TRANSITION_LENGTH animations:^{
_toast.alpha = TOAST_ALPHA_DEFAULT;
}];
}
-(void)hideSelf{
[UIView animateWithDuration:TOAST_TRANSITION_LENGTH animations:^{
_toast.alpha = 0.0f;
}completion:^(BOOL finished){
if (finished)[_toast removeFromSuperview];
}];
}
-(BOOL) isEqual:(id)object{
JToast *passedToast = (JToast *)object;
if ([passedToast.message isEqualToString:self.message]
&&(-MIN_DIFF_FOR_ANIM_LENGTH<=(passedToast.animLength-self.animLength)&&(passedToast.animLength-self.animLength)<=MIN_DIFF_FOR_ANIM_LENGTH)
&&passedToast.position
&&passedToast.parent==self.parent) return YES;
return NO;
}
@end