diff --git a/RMessage.podspec b/RMessage.podspec index 9e34ad1..ddf6c08 100644 --- a/RMessage.podspec +++ b/RMessage.podspec @@ -9,7 +9,7 @@ Pod::Spec.new do |s| s.name = "RMessage" - s.version = "2.3.1" + s.version = "2.3.2" s.summary = "Easy to use and customizable messages/notifications for iOS" s.description = <<-DESC This framework provides an easy to use class to show little notification views on the top of the screen. diff --git a/RMessage/RMessageView.m b/RMessage/RMessageView.m index 0c198f1..6928f22 100644 --- a/RMessage/RMessageView.m +++ b/RMessage/RMessageView.m @@ -9,7 +9,6 @@ #import "RMessageView.h" #import "HexColors.h" #import "UIViewController+PPTopMostController.h" -#import static NSString *const RDesignFileName = @"RMessageDefaultDesign"; @@ -28,8 +27,7 @@ @interface RMessageView () @property (nonatomic, weak) IBOutlet UILabel *subtitleLabel; @property (nonatomic, weak) IBOutlet NSLayoutConstraint *titleSubtitleVerticalSpacingConstraint; @property (nonatomic, weak) IBOutlet NSLayoutConstraint *titleSubtitleContainerViewTrailingConstraint; -@property (nonatomic, weak) IBOutlet NSLayoutConstraint *titleSubtitleContainerViewTopConstraint; -@property (nonatomic, weak) IBOutlet NSLayoutConstraint *titleSubtitleContainerViewBottomConstraint; +@property (nonatomic, strong) NSLayoutConstraint *titleSubtitleContainerViewLayoutGuideConstraint; @property (nonatomic, weak) IBOutlet NSLayoutConstraint *titleLabelLeadingConstraint; @property (nonatomic, weak) IBOutlet NSLayoutConstraint *titleLabelTrailingConstraint; @@ -95,6 +93,8 @@ @interface RMessageView () iOS changing the overbounce dynamically according to view size. */ @property (nonatomic, assign) CGFloat springAnimationPadding; +@property (nonatomic, assign) BOOL springAnimationPaddingCalculated; + @end @implementation RMessageView @@ -138,11 +138,6 @@ + (void)addDesignsFromFileWithName:(NSString *)filename inBundle:(NSBundle *)bun } } -+ (BOOL)isNavigationBarHiddenForNavigationController:(UINavigationController *)navController -{ - return (navController.navigationBarHidden || navController.navigationBar.isHidden); -} - + (UIViewController *)defaultViewController { UIViewController *viewController = [UIViewController topMostController]; @@ -152,30 +147,6 @@ + (UIViewController *)defaultViewController return viewController; } -/** - Method which determines if viewController edges extend under top bars - (navigation bars for example). There are various scenarios and even iOS bugs in which view - controllers that ask to present under top bars don't truly do but this method hopes to properly - catch all these bugs and scenarios and let its caller know. - @return YES if viewController - */ -+ (BOOL)viewControllerEdgesExtendUnderTopBars:(UIViewController *)viewController -{ - if (viewController.edgesForExtendedLayout != UIRectEdgeTop && - viewController.edgesForExtendedLayout != UIRectEdgeAll) { - return NO; - } - - /* When a table view controller asks to extend under top bars, if the navigation bar is - translucent iOS will not extend the edges of the table view controller under the top bars. */ - if ([viewController isKindOfClass:[UITableViewController class]] && - !viewController.navigationController.navigationBar.translucent) { - return NO; - } - - return YES; -} - + (UIColor *)colorForString:(NSString *)string { if (string) return [UIColor hx_colorWithHexRGBAString:string alpha:1.f]; @@ -210,7 +181,7 @@ + (void)activateConstraints:(NSArray *)constraints inSuperview:(UIView *)supervi { if (!constraints || !superview) return; if (@available(iOS 8.0, *)) { - for (NSLayoutConstraint *constraint in constraints) constraint.active = YES; + [NSLayoutConstraint activateConstraints:constraints]; } else { [superview addConstraints:constraints]; } @@ -220,7 +191,7 @@ + (void)deActivateConstraints:(NSArray *)constraints inSuperview:(UIView *)super { if (!constraints || !superview) return; if (@available(iOS 8.0, *)) { - for (NSLayoutConstraint *constraint in constraints) constraint.active = NO; + [NSLayoutConstraint deactivateConstraints:constraints]; } else { [superview removeConstraints:constraints]; } @@ -246,13 +217,6 @@ + (void)deActivateConstraint:(NSLayoutConstraint *)constraint inSuperview:(UIVie } } -+ (NSString *)deviceName -{ - struct utsname systemInfo; - uname(&systemInfo); - return [NSString stringWithCString:systemInfo.machine encoding:NSUTF8StringEncoding]; -} - #pragma mark - Instance Methods - (instancetype)initWithDelegate:(id)delegate @@ -324,7 +288,7 @@ - (instancetype)initWithDelegate:(id)delegate _dismissCompletionCallback = dismissCompletionCallback; _titleSubtitleLabelsSizeToFit = NO; _dismissingEnabled = dismissingEnabled; - _springAnimationPadding = 10.f; + _springAnimationPadding = 5.f; NSError *designError = [self setupDesignDictionariesWithMessageType:_messageType customTypeName:customTypeName]; if (designError) return nil; @@ -496,14 +460,17 @@ - (void)setupLayout self.translatesAutoresizingMaskIntoConstraints = NO; if (!_title || !_subtitle) self.titleSubtitleVerticalSpacingConstraint.constant = 0; + [self calculateSpringAnimationPadding]; + [self setupTitleSubtitleContainerViewLayoutGuideConstraint]; + [self setupFinalAnimationConstraints]; + // Add RMessage to superview and prepare the ending constraints - [self layoutMessageForPresentation]; + if (!self.superview) [self.viewController.view addSubview:self]; // Prepare the starting y position constraints [self setupStartingAnimationConstraints]; - [self accommodateLayoutForSpringAnimationPadding]; - [self setupFinalAnimationConstraints]; + NSAssert(self.superview != nil, @"instance must have a superview by this point"); NSLayoutConstraint *centerXConstraint = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual @@ -524,17 +491,39 @@ - (void)setupLayout toItem:self.superview attribute:NSLayoutAttributeTrailing multiplier:1.f - constant:0.f]; + constant:0.f]; [[self class] - activateConstraints:@[centerXConstraint, leadingConstraint, trailingConstraint, self.topToVCLayoutConstraint] + activateConstraints:@[centerXConstraint, leadingConstraint, trailingConstraint, + self.titleSubtitleContainerViewLayoutGuideConstraint, self.topToVCLayoutConstraint] inSuperview:self.superview]; if (self.shouldBlurBackground) { - if (@available(iOS 8.0, *)) { - [self setupBlurBackground]; - } + [self setupBlurBackground]; } } +- (void)setupTitleSubtitleContainerViewLayoutGuideConstraint { + // Install a constraint that guarantees the title subtitle container view is properly spaced from the top layout guide + // when animating from top or the bottom layout guide when animating from bottom + if (self.messagePosition != RMessagePositionBottom) { + self.titleSubtitleContainerViewLayoutGuideConstraint = [NSLayoutConstraint constraintWithItem:self.titleSubtitleContainerView + attribute:NSLayoutAttributeTop + relatedBy:NSLayoutRelationEqual + toItem:self.viewController.topLayoutGuide + attribute:NSLayoutAttributeBottom + multiplier:1.f + constant:10.f]; + + } else { + self.titleSubtitleContainerViewLayoutGuideConstraint = [NSLayoutConstraint constraintWithItem:self.titleSubtitleContainerView + attribute:NSLayoutAttributeBottom + relatedBy:NSLayoutRelationEqual + toItem:self.viewController.bottomLayoutGuide + attribute:NSLayoutAttributeTop + multiplier:1.f + constant:-10.f]; + } + self.titleSubtitleContainerViewLayoutGuideConstraint.priority = 749; +} - (void)setupBackgroundImageViewWithImage:(UIImage *)image { _backgroundImageView = [[UIImageView alloc] initWithImage:image]; @@ -559,33 +548,35 @@ - (void)setupBackgroundImageViewWithImage:(UIImage *)image - (void)setupBlurBackground { - UIBlurEffect *blurEffect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight]; - UIVisualEffectView *blurView = [[UIVisualEffectView alloc] initWithEffect:blurEffect]; - blurView.translatesAutoresizingMaskIntoConstraints = NO; - [self insertSubview:blurView atIndex:0]; - NSArray *hConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[blurBackgroundView]-0-|" - options:0 - metrics:nil - views:@{ - @"blurBackgroundView": blurView - }]; - NSArray *vConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[blurBackgroundView]-0-|" - options:0 - metrics:nil - views:@{ - @"blurBackgroundView": blurView - }]; - [[self class] activateConstraints:hConstraints inSuperview:self]; - [[self class] activateConstraints:vConstraints inSuperview:self]; -} - -- (void)setupTitleSubtitleLabelsLayoutWidth + if (@available(iOS 8.0, *)) { + UIBlurEffect *blurEffect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight]; + UIVisualEffectView *blurView = [[UIVisualEffectView alloc] initWithEffect:blurEffect]; + blurView.translatesAutoresizingMaskIntoConstraints = NO; + [self insertSubview:blurView atIndex:0]; + NSArray *hConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[blurBackgroundView]-0-|" + options:0 + metrics:nil + views:@{ + @"blurBackgroundView": blurView + }]; + NSArray *vConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[blurBackgroundView]-0-|" + options:0 + metrics:nil + views:@{ + @"blurBackgroundView": blurView + }]; + [[self class] activateConstraints:hConstraints inSuperview:self]; + [[self class] activateConstraints:vConstraints inSuperview:self]; + } +} + +- (void)setupTitleSubtitleLabelsLayoutWidthWithSuperview:(nonnull UIView *)superview { CGFloat accessoryViewsAndPadding = 0.f; if (_iconImage) accessoryViewsAndPadding = _iconImage.size.width + 15.f; if (_button) accessoryViewsAndPadding += _button.bounds.size.width + 15.f; - CGFloat preferredLayoutWidth = self.superview.bounds.size.width - accessoryViewsAndPadding - 30.f; + CGFloat preferredLayoutWidth = superview.bounds.size.width - accessoryViewsAndPadding - 30.f; if (_titleSubtitleLabelsSizeToFit) { // Get the biggest occupied width of the two strings, set the max preferred layout width to that of the longest label @@ -637,7 +628,8 @@ - (void)layoutSubviews if (self.viewCornerRadius >= 0) { self.layer.cornerRadius = self.viewCornerRadius; } - [self setupTitleSubtitleLabelsLayoutWidth]; + + [self setupTitleSubtitleLabelsLayoutWidthWithSuperview: self.superview]; } - (void)setupDesignDefaults @@ -983,6 +975,13 @@ - (void)setupIconImageView attribute:NSLayoutAttributeLeading multiplier:1.f constant:-15.f]; + NSLayoutConstraint *imgViewTop = [NSLayoutConstraint constraintWithItem:self.iconImageView + attribute:NSLayoutAttributeTop + relatedBy:NSLayoutRelationGreaterThanOrEqual + toItem:self + attribute:NSLayoutAttributeTop + multiplier:1.f + constant:10.f]; NSLayoutConstraint *imgViewBottom = [NSLayoutConstraint constraintWithItem:self.iconImageView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationLessThanOrEqual @@ -991,7 +990,8 @@ - (void)setupIconImageView multiplier:1.f constant:-10.f]; [self addSubview:self.iconImageView]; - [[self class] activateConstraints:@[imgViewCenterY, imgViewLeading, imgViewTrailing, imgViewBottom] inSuperview:self]; + [[self class] activateConstraints:@[imgViewCenterY, imgViewLeading, imgViewTrailing, imgViewTop, imgViewBottom] + inSuperview:self]; } - (void)setupGestureRecognizers @@ -1047,60 +1047,34 @@ - (void)present } } -- (void)layoutMessageForPresentation +- (void)calculateSpringAnimationPadding { - _titleSubtitleContainerViewTopConstraint.constant = 10.f; - - CGFloat statusBarHeight = self.viewController.prefersStatusBarHidden ? 0.f : - ([UIApplication sharedApplication].statusBarFrame.size.height); - CGFloat bottomOffset = 0.f; - if (@available(iOS 11.0, *)) { - statusBarHeight = self.viewController.view.safeAreaInsets.top; - bottomOffset = self.viewController.view.safeAreaInsets.bottom; - - // Tweak the position to reduce what looks like visual overpadding on all devices with a notch. - // Unfortunately because we can't tell what device is running in the simulator this visual tweak - // will not show for notch devices running in the simulator unless you add the @"x86_64" or @"x86_32" - // value during simulator testing of notch devices. -// NSArray *notchDevices = @[@"iPhone10,3", @"iPhone10,6", @"x86_64"]; - NSArray *notchDevices = @[@"iPhone10,3", @"iPhone10,6"]; - NSString *deviceName = [[self class] deviceName]; - for (NSString *notchDevice in notchDevices) { - if ([notchDevice isEqualToString:deviceName]) { - statusBarHeight = self.viewController.view.safeAreaInsets.top - 10.f; - bottomOffset = self.viewController.view.safeAreaInsets.bottom - 10.f; - break; - } - } + if (_designDictionary[@"disableSpringAnimationPadding"] && + [_designDictionary[@"disableSpringAnimationPadding"] isKindOfClass:[NSNumber class]]) { + self.disableSpringAnimationPadding = [_designDictionary[@"disableSpringAnimationPadding"] boolValue]; } - UINavigationController *messageNavigationController = [self rootNavigationController]; - - if (self.messagePosition != RMessagePositionBottom) { - if (messageNavigationController) { - BOOL messageNavigationBarHidden = - [[self class] isNavigationBarHiddenForNavigationController:messageNavigationController]; - - if (!messageNavigationBarHidden && self.messagePosition == RMessagePositionTop) { - // Present from below nav bar when presenting from the top and navigation bar is present - [messageNavigationController.view insertSubview:self belowSubview:messageNavigationController.navigationBar]; - } else { - /* Navigation bar hidden or being asked to present as nav bar overlay, so present above status bar and/or - navigation bar */ - self.titleSubtitleContainerViewTopConstraint.constant = 10.f + statusBarHeight; - } - } else { - self.titleSubtitleContainerViewTopConstraint.constant = 10.f + statusBarHeight; - } - } else { - // Message position is RMessagePositionBottom - self.titleSubtitleContainerViewBottomConstraint.constant = 10.f + bottomOffset; + if (self.disableSpringAnimationPadding) { + self.springAnimationPadding = 0.f; + self.springAnimationPaddingCalculated = YES; + return; } - if (!self.superview) [self.viewController.view addSubview:self]; + + // Pass in the expected superview since we don't have one yet + // Allow the labels to size themselves by telling them their layout width + [self setupTitleSubtitleLabelsLayoutWidthWithSuperview: self.viewController.view]; + + // Tell the view to relayout + [self layoutIfNeeded]; + // Base the spring animation padding on an estimated height considering we need the spring animation padding itself + // to truly calculate the height of the view. + self.springAnimationPadding = ceilf(self.bounds.size.height / 120) * 5; + self.springAnimationPaddingCalculated = YES; } - (void)setupStartingAnimationConstraints { + NSAssert(self.superview != nil, @"instance must have a superview by this point"); if (self.messagePosition != RMessagePositionBottom) { self.topToVCLayoutConstraint = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeBottom @@ -1123,98 +1097,32 @@ - (void)setupStartingAnimationConstraints - (void)setupFinalAnimationConstraints { - [self layoutIfNeeded]; - - UINavigationController *messageNavigationController = [self rootNavigationController]; - - if (messageNavigationController) { - BOOL messageNavigationBarHidden = - [[self class] isNavigationBarHiddenForNavigationController:messageNavigationController]; - - if (self.messagePosition != RMessagePositionBottom) { - if (!messageNavigationBarHidden && self.messagePosition == RMessagePositionTop) { - self.topToVCFinalConstraint = [NSLayoutConstraint constraintWithItem:self - attribute:NSLayoutAttributeTop - relatedBy:NSLayoutRelationEqual - toItem:messageNavigationController.navigationBar - attribute:NSLayoutAttributeBottom - multiplier:1.f - constant:-_springAnimationPadding]; - } else { - /* Navigation bar hidden or being asked to present as nav bar overlay, so present above status bar and/or - navigation bar */ - self.topToVCFinalConstraint = [NSLayoutConstraint constraintWithItem:self - attribute:NSLayoutAttributeTop - relatedBy:NSLayoutRelationEqual - toItem: self.superview - attribute:NSLayoutAttributeTop - multiplier:1.f - constant:-_springAnimationPadding]; - } - self.topToVCFinalConstraint.constant += [self customVerticalOffset]; - } else { - CGFloat offset = -[self customVerticalOffset]; - if (!messageNavigationController.isToolbarHidden) { - // If tool bar present animate above toolbar - offset -= messageNavigationController.toolbar.bounds.size.height; - } - self.topToVCFinalConstraint = [NSLayoutConstraint constraintWithItem:self - attribute:NSLayoutAttributeBottom - relatedBy:NSLayoutRelationEqual - toItem:self.superview - attribute:NSLayoutAttributeBottom - multiplier:1.f - constant:_springAnimationPadding]; - self.topToVCFinalConstraint.constant += offset; - } + NSAssert(self.springAnimationPaddingCalculated, @"spring animation padding must have been calculated by now!"); + id layoutGuide = nil; + NSLayoutAttribute viewAttribute = 0; + NSLayoutAttribute layoutGuideAttribute = 0; + CGFloat springAnimationPadding = 0; + + if (self.messagePosition == RMessagePositionBottom) { + viewAttribute = NSLayoutAttributeBottom; + layoutGuide = self.viewController.bottomLayoutGuide; + layoutGuideAttribute = NSLayoutAttributeBottom; + springAnimationPadding = self.springAnimationPadding; } else { - if (self.messagePosition == RMessagePositionBottom) { - self.topToVCFinalConstraint = [NSLayoutConstraint constraintWithItem:self - attribute:NSLayoutAttributeBottom - relatedBy:NSLayoutRelationEqual - toItem:self.superview - attribute:NSLayoutAttributeBottom - multiplier:1.f - constant:_springAnimationPadding]; - } else { - self.topToVCFinalConstraint = [NSLayoutConstraint constraintWithItem:self - attribute:NSLayoutAttributeTop - relatedBy:NSLayoutRelationEqual - toItem:self.superview - attribute:NSLayoutAttributeTop - multiplier:1.f - constant:-_springAnimationPadding]; - } - self.topToVCFinalConstraint.constant += - [self customVerticalOffset]; - } -} - -- (void)accommodateLayoutForSpringAnimationPadding -{ - if (_designDictionary[@"disableSpringAnimationPadding"] && - [_designDictionary[@"disableSpringAnimationPadding"] isKindOfClass:[NSNumber class]]) { - self.disableSpringAnimationPadding = [_designDictionary[@"disableSpringAnimationPadding"] boolValue]; - } - - if (self.disableSpringAnimationPadding) { - self.springAnimationPadding = 0.f; - return; + viewAttribute = NSLayoutAttributeTop; + layoutGuide = self.viewController.topLayoutGuide; + layoutGuideAttribute = NSLayoutAttributeTop; + springAnimationPadding = -self.springAnimationPadding; } - [self.titleLabel sizeToFit]; - [self.subtitleLabel sizeToFit]; - - // Base the spring animation padding on the estimated view height. - // Note: For some reason we can't get a good view bounds sizing in this method until after we set the two properties - // this method sets. Given the chicken/egg problem this presents we use an estimated view height for the padding - // which is fine for these purposes. - CGFloat estimatedHeight = self.titleLabel.bounds.size.height + self.subtitleLabel.bounds.size.height + 5.f + 20.f; - _springAnimationPadding = ceilf(estimatedHeight / 120) * 10; - if (self.messagePosition != RMessagePositionBottom) { - self.titleSubtitleContainerViewTopConstraint.constant += _springAnimationPadding; - } else { - self.titleSubtitleContainerViewBottomConstraint.constant += _springAnimationPadding; - } + self.topToVCFinalConstraint = [NSLayoutConstraint constraintWithItem:self + attribute:viewAttribute + relatedBy:NSLayoutRelationEqual + toItem:layoutGuide + attribute:layoutGuideAttribute + multiplier:1.f + constant:springAnimationPadding]; + self.topToVCFinalConstraint.constant += [self customVerticalOffset]; } - (void)animateMessage @@ -1285,16 +1193,6 @@ - (void)dismissWithCompletion:(void (^)(void))completionBlock #pragma mark - Misc methods -- (UINavigationController *)rootNavigationController -{ - if ([self.viewController isKindOfClass:[UINavigationController class]]) { - return (UINavigationController *)self.viewController; - } else if ([self.viewController.parentViewController isKindOfClass:[UINavigationController class]]) { - return (UINavigationController *)self.viewController.parentViewController; - } - return nil; -} - - (void)buttonTapped { if (self.buttonCallback) self.buttonCallback(); diff --git a/RMessage/Resources/RMessageView.xib b/RMessage/Resources/RMessageView.xib index 35447d0..8169b5c 100644 --- a/RMessage/Resources/RMessageView.xib +++ b/RMessage/Resources/RMessageView.xib @@ -1,10 +1,11 @@ - + - + + @@ -15,7 +16,7 @@ - + - - + - + - + + @@ -65,8 +66,6 @@ - -