// // SPAlertController.m // SPAlertController // // Created by 乐升平 on 18/10/12. https://github.com/SPStore/SPAlertController // Copyright © 2018-2019 leshengping (lesp163@163.com). All rights reserved. // #import "SPAlertController.h" #define SP_SCREEN_WIDTH [UIScreen mainScreen].bounds.size.width #define SP_SCREEN_HEIGHT [UIScreen mainScreen].bounds.size.height #define SP_LINE_WIDTH 1.0 / [UIScreen mainScreen].scale #define Is_iPhoneX MAX(SP_SCREEN_WIDTH, SP_SCREEN_HEIGHT) >= 812 #define SP_STATUS_BAR_HEIGHT (Is_iPhoneX ? 44 : 20) #define SP_ACTION_TITLE_FONTSIZE 18 #define SP_ACTION_HEIGHT 55.0 @interface SPColorStyle : NSObject + (UIColor *)normalColor; + (UIColor *)selectedColor; + (UIColor *)lineColor; + (UIColor *)line2Color; + (UIColor *)lightLineColor; + (UIColor *)darkLineColor; + (UIColor *)lightWhite_DarkBlackColor; + (UIColor *)lightBlack_DarkWhiteColor; + (UIColor *)textViewBackgroundColor; + (UIColor *)alertRedColor; + (UIColor *)grayColor; + (UIColor *)colorPairsWithDynamicLightColor:(UIColor *)lightColor darkColor:(UIColor *)darkColor; + (UIColor *)colorPairsWithStaticLightColor:(UIColor *)lightColor darkColor:(UIColor *)darkColor; @end @implementation SPColorStyle + (UIColor *)normalColor { return [self colorPairsWithDynamicLightColor:[[UIColor whiteColor] colorWithAlphaComponent:0.7] darkColor:[UIColor colorWithRed:44.0 / 255.0 green:44.0 / 255.0 blue:44.0 / 255.0 alpha:1.0]]; } + (UIColor *)selectedColor { return [self colorPairsWithDynamicLightColor:[[UIColor grayColor] colorWithAlphaComponent:0.1] darkColor:[UIColor colorWithRed:55.0 / 255.0 green:55.0 / 255.0 blue:55.0 / 255.0 alpha:1.0]]; } + (UIColor *)lineColor { return [self colorPairsWithDynamicLightColor:[self lightLineColor] darkColor:[self darkLineColor]]; } + (UIColor *)line2Color { return [self colorPairsWithDynamicLightColor:[[UIColor grayColor] colorWithAlphaComponent:0.15] darkColor:[UIColor colorWithRed:29.0 / 255.0 green:29.0 / 255.0 blue:29.0 / 255.0 alpha:1.0]]; } + (UIColor *)lightWhite_DarkBlackColor { return [self colorPairsWithDynamicLightColor:[UIColor whiteColor] darkColor:[UIColor blackColor]]; } + (UIColor *)lightBlack_DarkWhiteColor { return [self colorPairsWithDynamicLightColor:[UIColor blackColor] darkColor:[UIColor whiteColor]]; } + (UIColor *)lightLineColor { return [[UIColor grayColor] colorWithAlphaComponent:0.3]; } + (UIColor *)darkLineColor { return [UIColor colorWithRed:60.0 / 255.0 green:60.0 / 255.0 blue:60.0 / 255.0 alpha:1.0]; } + (UIColor *)textViewBackgroundColor { return [self colorPairsWithDynamicLightColor:[UIColor colorWithRed:247.0 / 255.0 green:247.0 / 255.0 blue:247.0 / 255.0 alpha:1.0] darkColor:[UIColor colorWithRed:54.0 / 255.0 green:54.0 / 255.0 blue:54.0 / 255.0 alpha:1.0]]; } + (UIColor *)alertRedColor { return [UIColor systemRedColor]; } + (UIColor *)grayColor { return [UIColor grayColor]; } + (UIColor *)colorPairsWithDynamicLightColor:(UIColor *)lightColor darkColor:(UIColor *)darkColor { if (@available(iOS 13.0, *)) { return [UIColor colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull traitCollection) { if(traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) { return darkColor; } else { return lightColor; } }]; } else { return lightColor; } } + (UIColor *)colorPairsWithStaticLightColor:(UIColor *)lightColor darkColor:(UIColor *)darkColor { if (@available(iOS 13.0, *)) { UIUserInterfaceStyle mode = UITraitCollection.currentTraitCollection.userInterfaceStyle; if (mode == UIUserInterfaceStyleDark) { return darkColor; } else if (mode == UIUserInterfaceStyleLight) { return lightColor; } else { return lightColor; } } return lightColor; } @end #pragma mark ---------------------------- SPAlertAction begin -------------------------------- #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" @interface SPAlertAction() @property (nonatomic, assign) SPAlertActionStyle style; @property (nonatomic, copy) void (^handler)(SPAlertAction *action); // 当在addAction之后设置action属性时,会回调这个block,设置相应控件的字体、颜色等 // 如果没有这个block,那使用时,只有在addAction之前设置action的属性才有效 @property (nonatomic, copy) void (^propertyChangedBlock)(SPAlertAction *action, BOOL needUpdateConstraints); @end @implementation SPAlertAction // 由于要对装载action的数组进行拷贝,所以SPAlertAction也需要支持拷贝 - (id)copyWithZone:(NSZone *)zone { SPAlertAction *action = [[[self class] alloc] init]; action.title = self.title; action.attributedTitle = self.attributedTitle; action.image = self.image; action.imageTitleSpacing = self.imageTitleSpacing; action.style = self.style; action.enabled = self.enabled; action.titleColor = self.titleColor; action.titleFont = self.titleFont; action.titleEdgeInsets = self.titleEdgeInsets; action.handler = self.handler; action.propertyChangedBlock = self.propertyChangedBlock; return action; } + (instancetype)actionWithTitle:(nullable NSString *)title style:(SPAlertActionStyle)style handler:(void (^ __nullable)(SPAlertAction *action))handler { SPAlertAction *action = [[self alloc] initWithTitle:title style:(SPAlertActionStyle)style handler:handler]; return action; } - (instancetype)initWithTitle:(nullable NSString *)title style:(SPAlertActionStyle)style handler:(void (^ __nullable)(SPAlertAction *action))handler { self = [self init]; self.title = title; self.style = style; self.handler = handler; if (style == SPAlertActionStyleDestructive) { self.titleColor = [SPColorStyle alertRedColor]; self.titleFont = [UIFont systemFontOfSize:SP_ACTION_TITLE_FONTSIZE]; } else if (style == SPAlertActionStyleCancel) { self.titleColor = [SPColorStyle lightBlack_DarkWhiteColor]; self.titleFont = [UIFont boldSystemFontOfSize:SP_ACTION_TITLE_FONTSIZE]; } else { self.titleColor = [SPColorStyle lightBlack_DarkWhiteColor]; self.titleFont = [UIFont systemFontOfSize:SP_ACTION_TITLE_FONTSIZE]; } return self; } - (instancetype)init { if (self = [super init]) { [self initialize]; } return self; } - (void)initialize { _enabled = YES; // 默认能点击 _titleColor = [SPColorStyle lightBlack_DarkWhiteColor]; _titleFont = [UIFont systemFontOfSize:SP_ACTION_TITLE_FONTSIZE]; _titleEdgeInsets = UIEdgeInsetsMake(0, 15, 0, 15); } - (void)setTitle:(NSString *)title { _title = title; if (self.propertyChangedBlock) { self.propertyChangedBlock(self, YES); } } - (void)setAttributedTitle:(NSAttributedString *)attributedTitle { _attributedTitle = attributedTitle; if (self.propertyChangedBlock) { self.propertyChangedBlock(self, YES); } } - (void)setImage:(UIImage *)image { _image = image; if (self.propertyChangedBlock) { self.propertyChangedBlock(self, YES); } } - (void)setImageTitleSpacing:(CGFloat)imageTitleSpacing { _imageTitleSpacing = imageTitleSpacing; if (self.propertyChangedBlock) { self.propertyChangedBlock(self, YES); } } - (void)setTitleColor:(UIColor *)titleColor { _titleColor = titleColor; if (self.propertyChangedBlock) { self.propertyChangedBlock(self,NO); // 颜色改变不需要更新布局 } } - (void)setTitleFont:(UIFont *)titleFont { _titleFont = titleFont; if (self.propertyChangedBlock) { self.propertyChangedBlock(self,YES); // 字体改变需要更新布局 } } - (void)setEnabled:(BOOL)enabled { _enabled = enabled; if (self.propertyChangedBlock) { self.propertyChangedBlock(self,NO); // enabled改变不需要更新布局 } } @end #pragma mark ---------------------------- SPAlertAction end ---------------------------- #pragma mark ---------------------------- SPInterfaceActionItemSeparatorView begin -------------------------------- @interface SPInterfaceActionItemSeparatorView : UIView @end @implementation SPInterfaceActionItemSeparatorView - (instancetype)init { if (self = [super init]) { self.backgroundColor = [SPColorStyle lineColor]; } return self; } - (void)layoutSubviews { [super layoutSubviews]; self.backgroundColor = MIN(self.frame.size.width, self.frame.size.height) > SP_LINE_WIDTH ? [SPColorStyle line2Color] : [SPColorStyle lineColor]; } @end #pragma mark ---------------------------- SPAlertControllerActionView end -------------------------------- #pragma mark ---------------------------- SPInterfaceHeaderScrollView begin ---------------------------- @interface SPInterfaceHeaderScrollView : UIScrollView @property (nonatomic, weak) UIView *contentView; @property (nonatomic, weak) UILabel *titleLabel; @property (nonatomic, weak) UILabel *messageLabel; @property (nonatomic, weak) UIImageView *imageView; @property (nonatomic, assign) CGSize imageLimitSize; @property (nonatomic, weak) UIStackView *textFieldView; @property (nonatomic, strong) NSMutableArray *textFields; @property (nonatomic, assign) UIEdgeInsets contentEdgeInsets; @property (nonatomic, copy) void(^headerViewSfeAreaDidChangBlock)(void); @end @implementation SPInterfaceHeaderScrollView - (instancetype)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { self.showsHorizontalScrollIndicator = NO; if (@available(iOS 11.0, *)) { self.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; } self.contentEdgeInsets = UIEdgeInsetsMake(20, 15, 20, 15); } return self; } - (void)addTextField:(UITextField *)textField { [self.textFields addObject:textField]; // 将textView添加到self.textFieldView中的布局队列中,UIStackView会根据设置的属性自动布局 [self.textFieldView addArrangedSubview:textField]; // 由于self.textFieldView是没有高度的,它的高度由子控件撑起,所以子控件必须要有高度 [[NSLayoutConstraint constraintWithItem:textField attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:30.0f] setActive:YES]; [self setNeedsUpdateConstraints]; } - (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection { [super traitCollectionDidChange:previousTraitCollection]; if (@available(iOS 13.0, *)) { // 设置CGColor,不要传previousTraitCollection,previousTraitCollection指的是上一次的模式 UIColor *resolvedColor = [[SPColorStyle lineColor] resolvedColorWithTraitCollection:self.traitCollection]; for (UITextField *textField in self.textFields) { textField.layer.borderColor = resolvedColor.CGColor; } } } - (NSMutableArray *)textFields { if (!_textFields) { _textFields = [[NSMutableArray alloc] init]; } return _textFields; } - (void)safeAreaInsetsDidChange { [super safeAreaInsetsDidChange]; CGFloat safeTop = self.safeAreaInsets.top < 20 ? 20 : self.safeAreaInsets.top+10; CGFloat safeLeft = self.safeAreaInsets.left < 15 ? 15 : self.safeAreaInsets.left; CGFloat safeBottom = self.safeAreaInsets.bottom < 20 ? 20 : self.safeAreaInsets.bottom+6; CGFloat safeRight = self.safeAreaInsets.right < 15 ? 15 : self.safeAreaInsets.right; _contentEdgeInsets = UIEdgeInsetsMake(safeTop, safeLeft, safeBottom, safeRight); // 这个block,主要是更新Label的最大预估宽度 if (self.headerViewSfeAreaDidChangBlock) { self.headerViewSfeAreaDidChangBlock(); } [self setNeedsUpdateConstraints]; } - (void)updateConstraints { [super updateConstraints]; UIView *contentView = self.contentView; // 对contentView布局 // 先移除旧约束,再添加新约束 [NSLayoutConstraint deactivateConstraints:self.constraints]; [NSLayoutConstraint deactivateConstraints:contentView.constraints]; [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[contentView]-0-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(contentView)]]; [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[contentView]-0-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(contentView)]]; [[NSLayoutConstraint constraintWithItem:contentView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeWidth multiplier:1.0 constant:0] setActive:YES]; NSLayoutConstraint *equalHeightConstraint = [NSLayoutConstraint constraintWithItem:contentView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeHeight multiplier:1.0 constant:0]; equalHeightConstraint.priority = 998.0f; // 优先级不能最高, 最顶层的父view有高度限制,如果子控件撑起后的高度大于限制高度,则scrollView滑动查看全部内容 equalHeightConstraint.active = YES; UIImageView *imageView = _imageView; UIStackView *textFieldView = _textFieldView; CGFloat leftMargin = self.contentEdgeInsets.left; CGFloat rightMargin = self.contentEdgeInsets.right; CGFloat topMargin = self.contentEdgeInsets.top; CGFloat bottomMargin = self.contentEdgeInsets.bottom; // 对iconView布局 if (imageView.image) { NSMutableArray *imageViewConstraints = [NSMutableArray array]; [imageViewConstraints addObject:[NSLayoutConstraint constraintWithItem:imageView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.f constant:MIN(imageView.image.size.width, _imageLimitSize.width)]]; [imageViewConstraints addObject:[NSLayoutConstraint constraintWithItem:imageView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.f constant:MIN(imageView.image.size.height, _imageLimitSize.height)]]; [imageViewConstraints addObject:[NSLayoutConstraint constraintWithItem:imageView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:contentView attribute:NSLayoutAttributeCenterX multiplier:1.f constant:0]]; [imageViewConstraints addObject:[NSLayoutConstraint constraintWithItem:imageView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:contentView attribute:NSLayoutAttributeTop multiplier:1.f constant:topMargin]]; if (_titleLabel.text.length || _titleLabel.attributedText.length) { [imageViewConstraints addObject:[NSLayoutConstraint constraintWithItem:imageView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:_titleLabel attribute:NSLayoutAttributeTop multiplier:1.f constant:-17]]; } else if (_messageLabel.text.length || _messageLabel.attributedText.length) { [imageViewConstraints addObject:[NSLayoutConstraint constraintWithItem:imageView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:_messageLabel attribute:NSLayoutAttributeTop multiplier:1.f constant:-17]]; } else if (_textFields.count) { [imageViewConstraints addObject:[NSLayoutConstraint constraintWithItem:imageView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:textFieldView attribute:NSLayoutAttributeTop multiplier:1.f constant:-17]]; } else { [imageViewConstraints addObject:[NSLayoutConstraint constraintWithItem:imageView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:contentView attribute:NSLayoutAttributeBottom multiplier:1.f constant:-bottomMargin]]; } [NSLayoutConstraint activateConstraints:imageViewConstraints]; } // 对titleLabel和messageLabel布局 NSMutableArray *titleLabelConstraints = [NSMutableArray array]; NSMutableArray *labels = [NSMutableArray array]; if (_titleLabel.text.length || _titleLabel.attributedText.length) { [labels insertObject:_titleLabel atIndex:0]; } if (_messageLabel.text.length || _messageLabel.attributedText.length) { [labels addObject:_messageLabel]; } [labels enumerateObjectsUsingBlock:^(UILabel *label, NSUInteger idx, BOOL * _Nonnull stop) { // 左右间距 [titleLabelConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:[NSString stringWithFormat:@"H:|-(==leftMargin)-[label]-(==rightMargin)-|"] options:0 metrics:@{@"leftMargin":@(leftMargin),@"rightMargin":@(rightMargin)} views:NSDictionaryOfVariableBindings(label)]]; // 第一个子控件顶部间距 if (idx == 0) { if (!imageView.image) { [titleLabelConstraints addObject:[NSLayoutConstraint constraintWithItem:label attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:contentView attribute:NSLayoutAttributeTop multiplier:1.f constant:topMargin]]; } } // 最后一个子控件底部间距 if (idx == labels.count - 1) { if (self.textFields.count) { [titleLabelConstraints addObject:[NSLayoutConstraint constraintWithItem:label attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:textFieldView attribute:NSLayoutAttributeTop multiplier:1.f constant:-bottomMargin]]; } else { [titleLabelConstraints addObject:[NSLayoutConstraint constraintWithItem:label attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:contentView attribute:NSLayoutAttributeBottom multiplier:1.f constant:-bottomMargin]]; } } // 子控件之间的垂直间距 if (idx > 0) { [titleLabelConstraints addObject:[NSLayoutConstraint constraintWithItem:label attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:labels[idx - 1] attribute:NSLayoutAttributeBottom multiplier:1.f constant:7.5]]; } }]; [NSLayoutConstraint activateConstraints:titleLabelConstraints]; if (self.textFields.count) { NSMutableArray *textFieldViewConstraints = [NSMutableArray array]; if (!labels.count && !imageView.image) { // 没有titleLabel、messageLabel和iconView,textFieldView的顶部相对contentView,否则不用写,因为前面写好了 [textFieldViewConstraints addObject:[NSLayoutConstraint constraintWithItem:textFieldView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:contentView attribute:NSLayoutAttributeTop multiplier:1.f constant:topMargin]]; } [textFieldViewConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:[NSString stringWithFormat:@"H:|-(==leftMargin)-[textFieldView]-(==rightMargin)-|"] options:0 metrics:@{@"leftMargin":@(leftMargin),@"rightMargin":@(rightMargin)} views:NSDictionaryOfVariableBindings(textFieldView)]]; [textFieldViewConstraints addObject:[NSLayoutConstraint constraintWithItem:textFieldView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:contentView attribute:NSLayoutAttributeBottom multiplier:1.f constant:-bottomMargin]]; [NSLayoutConstraint activateConstraints:textFieldViewConstraints]; } // systemLayoutSizeFittingSize:方法获取子控件撑起contentView后的高度,如果子控件是UILabel,那么子label必须设置preferredMaxLayoutWidth,否则当label多行文本时计算不准确 NSLayoutConstraint *contentViewHeightConstraint = [NSLayoutConstraint constraintWithItem:contentView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:[contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height]; contentViewHeightConstraint.active = YES; } - (UIView *)contentView { if (!_contentView) { UIView *contentView = [[UIView alloc] init]; contentView.translatesAutoresizingMaskIntoConstraints = NO; [self addSubview:contentView]; _contentView = contentView; } return _contentView; } - (UILabel *)titleLabel { if (!_titleLabel) { UILabel *titleLabel = [[UILabel alloc] init]; titleLabel.font = [UIFont boldSystemFontOfSize:18]; titleLabel.textAlignment = NSTextAlignmentCenter; titleLabel.textColor = [SPColorStyle lightBlack_DarkWhiteColor]; titleLabel.numberOfLines = 0; titleLabel.translatesAutoresizingMaskIntoConstraints = NO; [self.contentView addSubview:titleLabel]; _titleLabel = titleLabel; } return _titleLabel; } - (UILabel *)messageLabel { if (!_messageLabel) { UILabel *messageLabel = [[UILabel alloc] init]; messageLabel.font = [UIFont systemFontOfSize:18]; messageLabel.textAlignment = NSTextAlignmentCenter; messageLabel.textColor = [SPColorStyle grayColor]; messageLabel.numberOfLines = 0; messageLabel.translatesAutoresizingMaskIntoConstraints = NO; [self.contentView addSubview:messageLabel]; _messageLabel = messageLabel; } return _messageLabel; } - (UIImageView *)imageView { if (!_imageView) { UIImageView *imageView = [[UIImageView alloc] init]; imageView.translatesAutoresizingMaskIntoConstraints = NO; [self.contentView insertSubview:imageView atIndex:0]; _imageView = imageView; } return _imageView; } - (UIStackView *)textFieldView { if (!_textFieldView) { UIStackView *textFieldView = [[UIStackView alloc] init]; textFieldView.translatesAutoresizingMaskIntoConstraints = NO; textFieldView.distribution = UIStackViewDistributionFillEqually; textFieldView.axis = UILayoutConstraintAxisVertical; if (self.textFields.count) { [self.contentView addSubview:textFieldView]; } _textFieldView = textFieldView; } return _textFieldView; } @end #pragma mark ---------------------------- SPInterfaceHeaderScrollView end ---------------------------- #pragma mark ---------------------------- SPAlertControllerActionView begin -------------------------------- @interface SPAlertControllerActionView : UIView @property (nonatomic, weak) id target; @property (nonatomic, assign) SEL methodAction; @property (nonatomic, strong) SPAlertAction *action; @property (nonatomic, weak) UIButton *actionButton; @property (nonatomic, strong) NSMutableArray *actionButtonConstraints; @property (nonatomic, assign) CGFloat afterSpacing; - (void)addTarget:(id)target action:(SEL)action; @end @implementation SPAlertControllerActionView - (instancetype)init { if (self = [super init]) { _afterSpacing = SP_LINE_WIDTH; } return self; } - (void)setAction:(SPAlertAction *)action { _action = action; self.actionButton.titleLabel.font = action.titleFont; if (action.enabled) { [self.actionButton setTitleColor:action.titleColor forState:UIControlStateNormal]; } else { [self.actionButton setTitleColor:[action.titleColor colorWithAlphaComponent:0.4] forState:UIControlStateNormal]; } // 注意不能赋值给按钮的titleEdgeInsets,当只有文字时,按钮的titleEdgeInsets设置top和bottom值无效 self.actionButton.contentEdgeInsets = action.titleEdgeInsets; self.actionButton.enabled = action.enabled; self.actionButton.tintColor = action.tintColor; if (action.attributedTitle) { // 这里之所以要设置按钮颜色为黑色,是因为如果外界在addAction:之后设置按钮的富文本,那么富文本的颜色在没有采用NSForegroundColorAttributeName的情况下会自动读取按钮上普通文本的颜色,在addAction:之前设置会保持默认色(黑色),为了在addAction:前后设置富文本保持统一,这里先将按钮置为黑色,富文本就会是黑色 [self.actionButton setTitleColor:[SPColorStyle lightBlack_DarkWhiteColor] forState:UIControlStateNormal]; if ([action.attributedTitle.string containsString:@"\n"] || [action.attributedTitle.string containsString:@"\r"]) { self.actionButton.titleLabel.lineBreakMode = NSLineBreakByWordWrapping; } [self.actionButton setAttributedTitle:action.attributedTitle forState:UIControlStateNormal]; // 设置完富文本之后,还原按钮普通文本的颜色,其实这行代码加不加都不影响,只是为了让按钮普通文本的颜色保持跟action.titleColor一致 [self.actionButton setTitleColor:action.titleColor forState:UIControlStateNormal]; } else { if ([action.title containsString:@"\n"] || [action.title containsString:@"\r"]) { self.actionButton.titleLabel.lineBreakMode = NSLineBreakByWordWrapping; } [self.actionButton setTitle:action.title forState:UIControlStateNormal]; } [self.actionButton setImage:action.image forState:UIControlStateNormal]; self.actionButton.titleEdgeInsets = UIEdgeInsetsMake(0, action.imageTitleSpacing, 0, -action.imageTitleSpacing); self.actionButton.imageEdgeInsets = UIEdgeInsetsMake(0, -action.imageTitleSpacing, 0, action.imageTitleSpacing); } - (void)addTarget:(id)target action:(SEL)methodAction { _target = target; _methodAction = methodAction; } - (void)touchUpInside:(UIButton *)sender { // 用函数指针实现_target调用_methodAction,相当于[_target performSelector:_methodAction withObject:self];但是后者会报警告 SEL selector = _methodAction; IMP imp = [_target methodForSelector:selector]; void (*func)(id, SEL,SPAlertControllerActionView *) = (void *)imp; func(_target, selector, self); } - (void)touchDown:(UIButton *)sender { sender.backgroundColor = [SPColorStyle selectedColor]; } - (void)touchDragExit:(UIButton *)sender { sender.backgroundColor = [SPColorStyle normalColor]; } - (SPAlertController *)findAlertController { UIResponder *next = [self nextResponder]; do { if ([next isKindOfClass:[SPAlertController class]]) { return (SPAlertController *)next; } else { next = [next nextResponder]; } } while (next != nil); return nil; } // 安全区域发生了改变,在这个方法里自动适配iPhoneX - (void)safeAreaInsetsDidChange { [super safeAreaInsetsDidChange]; // safeAreaInsets+titleEdgeInsets self.actionButton.contentEdgeInsets = UIEdgeInsetsAddEdgeInsets(self.safeAreaInsets, _action.titleEdgeInsets); [self setNeedsUpdateConstraints]; } UIEdgeInsets UIEdgeInsetsAddEdgeInsets(UIEdgeInsets i1,UIEdgeInsets i2) { return UIEdgeInsetsMake(i1.top+i2.top, i1.left+i2.left, i1.bottom+i2.bottom, i1.right+i2.right); } - (void)updateConstraints { [super updateConstraints]; UIButton *actionButton = self.actionButton; if (self.actionButtonConstraints) { [NSLayoutConstraint deactivateConstraints:self.actionButtonConstraints]; self.actionButtonConstraints = nil; } NSMutableArray *actionButtonConstraints = [NSMutableArray array]; [actionButtonConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[actionButton]-0-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(actionButton)]]; [actionButtonConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[actionButton]-0-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(actionButton)]]; // 按钮必须确认高度,因为其父视图及父视图的父视图乃至根视图都没有设置高度,而且必须用NSLayoutRelationEqual,如果用NSLayoutRelationGreaterThanOrEqual,虽然也能撑起父视图,但是当某个按钮的高度有所变化以后,stackView会将其余按钮按的高度同比增减。 // titleLabel的内容自适应的高度 CGFloat labelH = actionButton.titleLabel.intrinsicContentSize.height; // 按钮的上下内边距之和 CGFloat topBottom_insetsSum = actionButton.contentEdgeInsets.top+actionButton.contentEdgeInsets.bottom; // 文字的上下间距之和,等于SP_ACTION_HEIGHT-默认字体大小,这是为了保证文字上下有一个固定间距值,不至于使文字靠按钮太紧,,由于按钮内容默认垂直居中,所以最终的顶部或底部间距为topBottom_marginSum/2.0,这个间距,几乎等于18号字体时,最小高度为49时的上下间距 CGFloat topBottom_marginSum = SP_ACTION_HEIGHT-[UIFont systemFontOfSize:SP_ACTION_TITLE_FONTSIZE].lineHeight; // 按钮高度 CGFloat buttonH = labelH+topBottom_insetsSum+topBottom_marginSum; UIStackView *stackView = (UIStackView *)self.superview; NSLayoutRelation relation = NSLayoutRelationEqual; if ([stackView isKindOfClass:[UIStackView class]] && stackView.axis == UILayoutConstraintAxisHorizontal) { relation = NSLayoutRelationGreaterThanOrEqual; } // 如果字体保持默认18号,只有一行文字时最终结果约等于SP_ACTION_HEIGHT NSLayoutConstraint *buttonHonstraint = [NSLayoutConstraint constraintWithItem:actionButton attribute:NSLayoutAttributeHeight relatedBy:relation toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:buttonH]; buttonHonstraint.priority = 999; [actionButtonConstraints addObject:buttonHonstraint]; // 给一个最小高度,当按钮字体很小时,如果还按照上面的高度计算,高度会比较小 NSLayoutConstraint *minHConstraint = [NSLayoutConstraint constraintWithItem:actionButton attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:SP_ACTION_HEIGHT+topBottom_insetsSum]; minHConstraint.priority = UILayoutPriorityRequired; [self addConstraints:actionButtonConstraints]; self.actionButtonConstraints = actionButtonConstraints; } - (UIButton *)actionButton { if (!_actionButton) { UIButton *actionButton = [UIButton buttonWithType:UIButtonTypeCustom]; actionButton.backgroundColor = [SPColorStyle normalColor]; actionButton.translatesAutoresizingMaskIntoConstraints = NO; actionButton.titleLabel.textAlignment = NSTextAlignmentCenter; actionButton.titleLabel.adjustsFontSizeToFitWidth = YES; actionButton.titleLabel.baselineAdjustment = UIBaselineAdjustmentAlignCenters; actionButton.titleLabel.minimumScaleFactor = 0.5; [actionButton addTarget:self action:@selector(touchUpInside:) forControlEvents:UIControlEventTouchUpInside]; // 手指按下然后在按钮有效事件范围内抬起 [actionButton addTarget:self action:@selector(touchDown:) forControlEvents:UIControlEventTouchDown | UIControlEventTouchDragInside]; // 手指按下或者手指按下后往外拽再往内拽 [actionButton addTarget:self action:@selector(touchDragExit:) forControlEvents:UIControlEventTouchDragExit | UIControlEventTouchUpOutside | UIControlEventTouchCancel]; // 手指被迫停止、手指按下后往外拽或者取消,取消的可能性:比如点击的那一刻突然来电话 [self addSubview:actionButton]; _actionButton = actionButton; } return _actionButton; } @end #pragma mark ---------------------------- SPAlertControllerActionView end -------------------------------- #pragma mark ---------------------------- SPInterfaceActionSequenceView begin -------------------------------- @interface SPInterfaceActionSequenceView : UIView @property (nonatomic, weak) UIScrollView *scrollView; @property (nonatomic, weak) UIView *contentView; @property (nonatomic, weak) UIView *cancelView; @property (nonatomic, weak) SPInterfaceActionItemSeparatorView *cancelActionLine; @property (nonatomic, weak) UIStackView *stackView; @property (nonatomic, strong) SPAlertAction *cancelAction; @property (nonatomic, strong) NSMutableArray *actionLineConstraints; @property (nonatomic, strong) NSMutableArray *actions; @property (nonatomic, assign) UIStackViewDistribution stackViewDistribution; @property (nonatomic, assign) UILayoutConstraintAxis axis; @property (nonatomic, copy) void (^buttonClickedInActionViewBlock)(NSInteger index); @end @implementation SPInterfaceActionSequenceView - (void)setAxis:(UILayoutConstraintAxis)axis { _axis = axis; self.stackView.axis = axis; [self setNeedsUpdateConstraints]; } - (void)setStackViewDistribution:(UIStackViewDistribution)stackViewDistribution { _stackViewDistribution = stackViewDistribution; self.stackView.distribution = stackViewDistribution; [self setNeedsUpdateConstraints]; } - (void)buttonClickedInActionView:(SPAlertControllerActionView *)actionView { NSInteger index = [self.actions indexOfObject:actionView.action]; if (self.buttonClickedInActionViewBlock) { self.buttonClickedInActionViewBlock(index); } } - (void)setCustomSpacing:(CGFloat)spacing afterActionIndex:(NSInteger)index { UIStackView *stackView = self.stackView; SPAlertControllerActionView *actionView = stackView.arrangedSubviews[index]; actionView.afterSpacing = spacing; if (@available(iOS 11.0, *)) { [self.stackView setCustomSpacing:spacing afterView:actionView]; } [self updateLineConstraints]; } - (CGFloat)customSpacingAfterActionIndex:(NSInteger)index { UIStackView *stackView = self.stackView; SPAlertControllerActionView *actionView = stackView.arrangedSubviews[index]; if (@available(iOS 11.0, *)) { return [self.stackView customSpacingAfterView:actionView]; } else { return 0.0; } } - (void)addAction:(SPAlertAction *)action { [self.actions addObject:action]; UIStackView *stackView = self.stackView; SPAlertControllerActionView *currentActionView = [[SPAlertControllerActionView alloc] init]; currentActionView.action = action; [currentActionView addTarget:self action:@selector(buttonClickedInActionView:)]; [stackView addArrangedSubview:currentActionView]; if (stackView.arrangedSubviews.count > 1) { // arrangedSubviews个数大于1,说明本次添加至少是第2次添加,此时要加一条分割线 [self addLineForStackView:stackView]; } [self setNeedsUpdateConstraints]; } - (void)addCancelAction:(SPAlertAction *)action { // 如果已经存在取消样式的按钮,则直接崩溃 NSAssert(!_cancelAction, @"SPAlertController can only have one action with a style of SPAlertActionStyleCancel"); _cancelAction = action; [self.actions addObject:action]; SPAlertControllerActionView *cancelActionView = [[SPAlertControllerActionView alloc] init]; cancelActionView.translatesAutoresizingMaskIntoConstraints = NO; cancelActionView.action = action; [cancelActionView addTarget:self action:@selector(buttonClickedInActionView:)]; [self.cancelView addSubview:cancelActionView]; [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[cancelActionView]-0-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(cancelActionView)]]; [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[cancelActionView]-0-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(cancelActionView)]]; [self setNeedsUpdateConstraints]; } // 为stackView添加分割线(细节) - (void)addLineForStackView:(UIStackView *)stackView { SPInterfaceActionItemSeparatorView *actionLine = [[SPInterfaceActionItemSeparatorView alloc] init]; actionLine.translatesAutoresizingMaskIntoConstraints = NO; // 这里必须用addSubview:,不能用addArrangedSubview:,因为分割线不参与排列布局 [stackView addSubview:actionLine]; } // 从一个数组筛选出不在另一个数组中的数组 - (NSArray *)filteredArrayFromArray:(NSArray *)array notInArray:(NSArray *)otherArray { NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NOT (SELF in %@)", otherArray]; // 用谓词语句筛选出所有的分割线 NSArray *subArray = [array filteredArrayUsingPredicate:predicate]; return subArray; } // 更新分割线约束(细节) - (void)updateLineConstraints { UIStackView *stackView = self.stackView; NSArray *arrangedSubviews = stackView.arrangedSubviews; if (arrangedSubviews.count <= 1) return; // 用谓词语句筛选出所有的分割线 NSArray *lines = [self filteredArrayFromArray:stackView.subviews notInArray:stackView.arrangedSubviews]; if (arrangedSubviews.count < lines.count) return; NSMutableArray *actionLineConstraints = [NSMutableArray array]; if (self.actionLineConstraints) { [NSLayoutConstraint deactivateConstraints:self.actionLineConstraints]; self.actionLineConstraints = nil; } for (int i = 0; i < lines.count; i++) { SPInterfaceActionItemSeparatorView *actionLine = lines[i]; SPAlertControllerActionView *actionView1 = arrangedSubviews[i]; SPAlertControllerActionView *actionView2 = arrangedSubviews[i+1]; if (self.axis == UILayoutConstraintAxisHorizontal) { [actionLineConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[actionLine]-0-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(actionLine)]]; [actionLineConstraints addObject:[NSLayoutConstraint constraintWithItem:actionLine attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:actionView1 attribute:NSLayoutAttributeRight multiplier:1.0 constant:0]]; [actionLineConstraints addObject:[NSLayoutConstraint constraintWithItem:actionLine attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:actionView2 attribute:NSLayoutAttributeLeft multiplier:1.0 constant:0]]; [actionLineConstraints addObject:[NSLayoutConstraint constraintWithItem:actionLine attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:actionView1.afterSpacing]]; } else { [actionLineConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[actionLine]-0-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(actionLine)]]; [actionLineConstraints addObject:[NSLayoutConstraint constraintWithItem:actionLine attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:actionView1 attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0]]; [actionLineConstraints addObject:[NSLayoutConstraint constraintWithItem:actionLine attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:actionView2 attribute:NSLayoutAttributeTop multiplier:1.0 constant:0]]; [actionLineConstraints addObject:[NSLayoutConstraint constraintWithItem:actionLine attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:actionView1.afterSpacing]]; } } [NSLayoutConstraint activateConstraints:actionLineConstraints]; self.actionLineConstraints = actionLineConstraints; } - (void)updateConstraints { [super updateConstraints]; UIView *scrollView = self.scrollView; UIView *contentView = self.contentView; UIView *cancelView = self.cancelView; SPInterfaceActionItemSeparatorView *cancelActionLine = self.cancelActionLine; [NSLayoutConstraint deactivateConstraints:self.constraints]; if (scrollView && scrollView.superview) { // 对scrollView布局 NSMutableArray *scrollViewConstraints = [NSMutableArray array]; [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[scrollView]-0-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(scrollView)]]; [scrollViewConstraints addObject:[NSLayoutConstraint constraintWithItem:scrollView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeTop multiplier:1.0 constant:0]]; if (cancelActionLine.superview) { [scrollViewConstraints addObject:[NSLayoutConstraint constraintWithItem:scrollView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:cancelActionLine attribute:NSLayoutAttributeTop multiplier:1.0 constant:0]]; } else { [scrollViewConstraints addObject:[NSLayoutConstraint constraintWithItem:scrollView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0]]; } [NSLayoutConstraint activateConstraints:scrollViewConstraints]; [NSLayoutConstraint deactivateConstraints:scrollView.constraints]; // 对contentView布局 [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[contentView]-0-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(contentView)]]; [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[contentView]-0-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(contentView)]]; [[NSLayoutConstraint constraintWithItem:contentView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:scrollView attribute:NSLayoutAttributeWidth multiplier:1.0 constant:0] setActive:YES]; NSLayoutConstraint *equalHeightConstraint = [NSLayoutConstraint constraintWithItem:contentView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:scrollView attribute:NSLayoutAttributeHeight multiplier:1.0 constant:0]; // 计算scrolView的最小和最大高度,下面这个if语句是保证当actions的g总个数大于4时,scrollView的高度至少为4个半SP_ACTION_HEIGHT的高度,否则自适应内容 CGFloat minHeight = 0.0; if (_axis == UILayoutConstraintAxisVertical) { if (self.cancelAction) { if (self.actions.count > 4) { // 如果有取消按钮且action总个数大于4,则除去取消按钮之外的其余部分的高度至少为3个半SP_ACTION_HEIGHT的高度,即加上取消按钮就是总高度至少为4个半SP_ACTION_HEIGHT的高度 minHeight = SP_ACTION_HEIGHT * 3.5; equalHeightConstraint.priority = 997.0f; // 优先级为997,必须小于998.0,因为头部如果内容过多时高度也会有限制,头部的优先级为998.0.这里定的规则是,当头部和action部分同时过多时,头部的优先级更高,但是它不能高到以至于action部分小于最小高度 } else { // 如果有取消按钮但action的个数大不于4,则该多高就显示多高 equalHeightConstraint.priority = 1000.0f; // 由子控件撑起 } } else { if (self.actions.count > 4) { minHeight = SP_ACTION_HEIGHT * 4.5; equalHeightConstraint.priority = 997.0f; } else { equalHeightConstraint.priority = 1000.0f; } } } else { minHeight = SP_ACTION_HEIGHT; } NSLayoutConstraint *minHeightConstraint = [NSLayoutConstraint constraintWithItem:scrollView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:minHeight]; minHeightConstraint.priority = 999.0;// 优先级不能大于对话框的最小顶部间距的优先级(999.0) minHeightConstraint.active = YES; equalHeightConstraint.active = YES; UIStackView *stackView = self.stackView; [NSLayoutConstraint deactivateConstraints:contentView.constraints]; // 对stackView布局 [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[stackView]-0-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(stackView)]]; [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[stackView]-0-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(stackView)]]; // 对stackView里面的分割线布局 [self updateLineConstraints]; } if (self.cancelActionLine.superview) { // cancelActionLine有superView则必有scrollView和cancelView NSMutableArray *cancelActionLineConstraints = [NSMutableArray array]; [cancelActionLineConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[cancelActionLine]-0-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(cancelActionLine)]]; [cancelActionLineConstraints addObject:[NSLayoutConstraint constraintWithItem:cancelActionLine attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:cancelView attribute:NSLayoutAttributeTop multiplier:1.0f constant:0]]; [cancelActionLineConstraints addObject:[NSLayoutConstraint constraintWithItem:cancelActionLine attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0f constant:8.0]]; [NSLayoutConstraint activateConstraints:cancelActionLineConstraints]; } // 对cancelView布局 if (self.cancelAction) { // 有取消样式的按钮才对cancelView布局 NSMutableArray *cancelViewConstraints = [NSMutableArray array]; [cancelViewConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[cancelView]-0-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(cancelView)]]; [cancelViewConstraints addObject:[NSLayoutConstraint constraintWithItem:cancelView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0]]; if (!self.cancelActionLine.superview) { [cancelViewConstraints addObject:[NSLayoutConstraint constraintWithItem:cancelView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeTop multiplier:1.0 constant:0]]; } [NSLayoutConstraint activateConstraints:cancelViewConstraints]; } } - (UIScrollView *)scrollView { if (!_scrollView) { UIScrollView *scrollView = [[UIScrollView alloc] init]; scrollView.showsHorizontalScrollIndicator = NO; scrollView.translatesAutoresizingMaskIntoConstraints = NO; if (@available(iOS 11.0, *)) { scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; } if ((self.cancelAction && self.actions.count > 1) || (!self.cancelAction && self.actions.count > 0)) { [self addSubview:scrollView]; } _scrollView = scrollView; } return _scrollView; } - (UIView *)contentView { if (!_contentView) { UIView *contentView = [[UIView alloc] init]; contentView.translatesAutoresizingMaskIntoConstraints = NO; [self.scrollView addSubview:contentView]; _contentView = contentView; } return _contentView; } - (UIStackView *)stackView { if (!_stackView) { UIStackView *stackView = [[UIStackView alloc] init]; stackView.translatesAutoresizingMaskIntoConstraints = NO; stackView.distribution = UIStackViewDistributionFillProportionally; stackView.spacing = SP_LINE_WIDTH; // 该间距腾出来的空间显示分割线 stackView.axis = UILayoutConstraintAxisVertical; [self.contentView addSubview:stackView]; _stackView = stackView; } return _stackView; } - (UIView *)cancelView { if (!_cancelView) { UIView *cancelView = [[UIView alloc] init]; cancelView.translatesAutoresizingMaskIntoConstraints = NO; if (self.cancelAction) { [self addSubview:cancelView]; } _cancelView = cancelView; } return _cancelView; } - (SPInterfaceActionItemSeparatorView *)cancelActionLine { if (!_cancelActionLine) { SPInterfaceActionItemSeparatorView *cancelActionLine = [[SPInterfaceActionItemSeparatorView alloc] init]; cancelActionLine.translatesAutoresizingMaskIntoConstraints = NO; if (self.cancelView.superview && self.scrollView.superview) { [self addSubview:cancelActionLine]; } _cancelActionLine = cancelActionLine; } return _cancelActionLine; } - (NSMutableArray *)actions { if (!_actions) { _actions = [[NSMutableArray alloc] init]; } return _actions; } @end #pragma mark ---------------------------- SPInterfaceActionSequenceView end -------------------------------- #pragma mark ---------------------------- SPAlertController begin -------------------------------- @interface SPAlertController () @property (nonatomic, strong) UIView *alertControllerView; @property (nonatomic, weak) UIView *containerView; @property (nonatomic, weak) UIView *alertView; @property (nonatomic, strong) UIView *customAlertView; @property (nonatomic, weak) SPInterfaceHeaderScrollView *headerView; @property (nonatomic, strong) UIView *customHeaderView; @property (nonatomic, weak) SPInterfaceActionSequenceView *actionSequenceView; @property (nonatomic, strong) UIView *customActionSequenceView; @property (nonatomic, strong) UIView *componentView; @property (nonatomic, assign) CGSize customViewSize; @property (nonatomic, weak) SPInterfaceActionItemSeparatorView *headerActionLine; @property (nonatomic, strong) NSMutableArray *headerActionLineConstraints; @property (nonatomic, weak) SPInterfaceActionItemSeparatorView *componentActionLine; @property (nonatomic, strong) NSMutableArray *componentViewConstraints; @property (nonatomic, strong) NSMutableArray *componentActionLineConstraints; @property (nonatomic, strong) UIView *dimmingKnockoutBackdropView; @property (nonatomic, strong) NSMutableArray *alertControllerViewConstraints; @property (nonatomic, strong) NSMutableArray *headerViewConstraints; @property (nonatomic, strong) NSMutableArray *actionSequenceViewConstraints; @property (nonatomic, assign) SPAlertControllerStyle preferredStyle; @property (nonatomic, assign) SPAlertAnimationType animationType; @property (nonatomic, assign) UIBlurEffectStyle backgroundViewAppearanceStyle; @property (nonatomic, assign) CGFloat backgroundViewAlpha; // action数组 @property (nonatomic) NSArray *actions; // textFiled数组 @property (nonatomic) NSArray *textFields; // 除去取消样式action的其余action数组 @property (nonatomic) NSMutableArray *otherActions; @property (nonatomic, assign) BOOL isForceLayout; // 是否强制排列,外界设置了actionAxis属性认为是强制 @property (nonatomic, assign) BOOL isForceOffset; // 是否强制偏移,外界设置了offsetForAlert属性认为是强制 @end @implementation SPAlertController @synthesize title = _title; #pragma mark - public + (instancetype)alertControllerWithTitle:(NSString *)title message:(NSString *)message preferredStyle:(SPAlertControllerStyle)preferredStyle { SPAlertController *alertVc = [[SPAlertController alloc] initWithTitle:title message:message customAlertView:nil customHeaderView:nil customActionSequenceView:nil componentView:nil preferredStyle:preferredStyle animationType:SPAlertAnimationTypeDefault]; return alertVc; } + (instancetype)alertControllerWithTitle:(NSString *)title message:(NSString *)message preferredStyle:(SPAlertControllerStyle)preferredStyle animationType:(SPAlertAnimationType)animationType { SPAlertController *alertVc = [[SPAlertController alloc] initWithTitle:title message:message customAlertView:nil customHeaderView:nil customActionSequenceView:nil componentView:nil preferredStyle:preferredStyle animationType:animationType]; return alertVc; } + (instancetype)alertControllerWithCustomAlertView:(UIView *)customAlertView preferredStyle:(SPAlertControllerStyle)preferredStyle animationType:(SPAlertAnimationType)animationType { SPAlertController *alertVc = [[SPAlertController alloc] initWithTitle:nil message:nil customAlertView:customAlertView customHeaderView:nil customActionSequenceView:nil componentView:nil preferredStyle:preferredStyle animationType:animationType]; return alertVc; } + (instancetype)alertControllerWithCustomHeaderView:(UIView *)customHeaderView preferredStyle:(SPAlertControllerStyle)preferredStyle animationType:(SPAlertAnimationType)animationType { SPAlertController *alertVc = [[SPAlertController alloc] initWithTitle:nil message:nil customAlertView:nil customHeaderView:customHeaderView customActionSequenceView:nil componentView:nil preferredStyle:preferredStyle animationType:animationType]; return alertVc; } + (instancetype)alertControllerWithCustomActionSequenceView:(UIView *)customActionSequenceView title:(nullable NSString *)title message:(nullable NSString *)message preferredStyle:(SPAlertControllerStyle)preferredStyle animationType:(SPAlertAnimationType)animationType { SPAlertController *alertVc = [[SPAlertController alloc] initWithTitle:title message:message customAlertView:nil customHeaderView:nil customActionSequenceView:customActionSequenceView componentView:nil preferredStyle:preferredStyle animationType:animationType]; return alertVc; } - (void)setOffsetForAlert:(CGPoint)offsetForAlert animated:(BOOL)animated { _offsetForAlert = offsetForAlert; _isForceOffset = YES; [self makeViewOffsetWithAnimated:animated]; } - (void)insertComponentView:(UIView *)componentView { _componentView = componentView; } // 添加action - (void)addAction:(SPAlertAction *)action { NSMutableArray *actions = self.actions.mutableCopy; [actions addObject:action]; self.actions = actions; if (self.preferredStyle == SPAlertControllerStyleAlert) { // alert样式不论是否为取消样式的按钮,都直接按顺序添加 if (action.style != SPAlertActionStyleCancel) { [self.otherActions addObject:action]; } [self.actionSequenceView addAction:action]; } else { // actionSheet样式 if (action.style == SPAlertActionStyleCancel) { // 如果是取消样式的按钮 [self.actionSequenceView addCancelAction:action]; } else { [self.otherActions addObject:action]; [self.actionSequenceView addAction:action]; } } if (!self.isForceLayout) { // 如果为NO,说明外界没有设置actionAxis,此时按照默认方式排列 if (self.preferredStyle == SPAlertControllerStyleAlert) { if (self.actions.count > _maxNumberOfActionHorizontalArrangementForAlert) { // alert样式下,action的个数大于2时垂直排列,这里不等式右边写_maxNumberOfActionHorizontalArrangementForAlert是为了让被废弃的_maxNumberOfActionHorizontalArrangementForAlert依然生效 _actionAxis = UILayoutConstraintAxisVertical; // 本框架任何一处都不允许调用actionAxis的setter方法,如果调用了则无法判断是外界调用还是内部调用 [self updateActionAxis]; } else { // action的个数小于等于2,action水平排列 _actionAxis = UILayoutConstraintAxisHorizontal; [self updateActionAxis]; } } else { // actionSheet样式下默认垂直排列 _actionAxis = UILayoutConstraintAxisVertical; [self updateActionAxis]; } } else { [self updateActionAxis]; } // 这个block是保证外界在添加action之后再设置action属性时依然生效;当使用时在addAction之后再设置action的属性时,会回调这个block __weak typeof(self) weakSelf = self; action.propertyChangedBlock = ^(SPAlertAction *action, BOOL needUpdateConstraints) { __strong typeof(self) strongSelf = weakSelf; if (strongSelf.preferredStyle == SPAlertControllerStyleAlert) { // alert样式下:arrangedSubviews数组和actions是对应的 NSInteger index = [strongSelf.actions indexOfObject:action]; SPAlertControllerActionView *actionView = [strongSelf.actionSequenceView.stackView.arrangedSubviews objectAtIndex:index]; if ([actionView isKindOfClass:[SPAlertControllerActionView class]]) { actionView.action = action; } if (strongSelf.presentationController.presentingViewController) { // 文字显示不全处理 [strongSelf handleIncompleteTextDisplay]; } } else { if (action.style == SPAlertActionStyleCancel) { // cancelView中只有唯一的一个actionView SPAlertControllerActionView *actionView = [strongSelf.actionSequenceView.cancelView.subviews lastObject]; if ([actionView isKindOfClass:[SPAlertControllerActionView class]]) { // 这个判断可以不加,加判断是防止有一天改动框架不小心在cancelView中加了新的view产生安全隐患 actionView.action = action; } } else { // actionSheet样式下:arrangedSubviews数组和otherActions是对应的 NSInteger index = [strongSelf.otherActions indexOfObject:action]; SPAlertControllerActionView *actionView = [strongSelf.actionSequenceView.stackView.arrangedSubviews objectAtIndex:index]; if ([actionView isKindOfClass:[SPAlertControllerActionView class]]) { actionView.action = action; } } } if (strongSelf.presentationController.presentingViewController && needUpdateConstraints) { // 如果在present完成后的某个时刻再去设置action的属性,字体等改变需要更新布局 [strongSelf.actionSequenceView setNeedsUpdateConstraints]; } }; } // 添加文本输入框 - (void)addTextFieldWithConfigurationHandler:(void (^)(UITextField * _Nonnull))configurationHandler { NSAssert(self.preferredStyle == SPAlertControllerStyleAlert,@"SPAlertController does not allow 'addTextFieldWithConfigurationHandler:' to be called in the style of SPAlertControllerStyleActionSheet"); UITextField *textField = [[UITextField alloc] init]; textField.translatesAutoresizingMaskIntoConstraints = NO; textField.backgroundColor = [SPColorStyle textViewBackgroundColor]; // 系统的UITextBorderStyleLine样式线条过于黑,所以自己设置 textField.layer.borderWidth = SP_LINE_WIDTH; // 这里设置的颜色是静态的,动态设置CGColor,还需要监听深浅模式的切换 textField.layer.borderColor = [SPColorStyle colorPairsWithStaticLightColor:[SPColorStyle lineColor] darkColor:[SPColorStyle darkLineColor]].CGColor; // 在左边设置一张view,充当光标左边的间距,否则光标紧贴textField不美观 textField.leftView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 5, 0)]; textField.leftView.userInteractionEnabled = NO; textField.leftViewMode = UITextFieldViewModeAlways; textField.font = [UIFont systemFontOfSize:14]; // 去掉textField键盘上部的联想条 textField.autocorrectionType = UITextAutocorrectionTypeNo; [textField addTarget:self action:@selector(textFieldDidEndOnExit:) forControlEvents:UIControlEventEditingDidEndOnExit]; NSMutableArray *array = self.textFields.mutableCopy; [array addObject:textField]; self.textFields = array; [self.headerView addTextField:textField]; if (configurationHandler) { configurationHandler(textField); } } - (void)setCustomSpacing:(CGFloat)spacing afterAction:(SPAlertAction *)action { if (@available(iOS 11.0, *)) { if (action == nil) return; if (action.style == SPAlertActionStyleCancel) { NSLog(@"*** warning in -[SPAlertController setCustomSpacing:afterAction:]: 'the -action must not be a action with SPAlertActionStyleCancel style'"); } else if (![self.otherActions containsObject:action]) { NSLog(@"*** warning in -[SPAlertController setCustomSpacing:afterAction:]: 'the -action must be contained in the -actions array, not a action with SPAlertActionStyleCancel style'"); } else { NSInteger index = [self.otherActions indexOfObject:action]; [self.actionSequenceView setCustomSpacing:spacing afterActionIndex:index]; } } else { // 报异常 [self doesNotRecognizeSelector:@selector(setCustomSpacing:afterAction:)]; } } - (CGFloat)customSpacingAfterAction:(SPAlertAction *)action { if (@available(iOS 11.0, *)) { if ([self.otherActions containsObject:action]) { NSInteger index = [self.otherActions indexOfObject:action]; return [self.actionSequenceView customSpacingAfterActionIndex:index]; } } else { // 报异常 [self doesNotRecognizeSelector:@selector(setCustomSpacing:afterAction:)]; } return 0.0; } - (void)setBackgroundViewAppearanceStyle:(UIBlurEffectStyle)style alpha:(CGFloat)alpha { _backgroundViewAppearanceStyle = style; _backgroundViewAlpha = alpha; } - (void)updateCustomViewSize:(CGSize)size { _customViewSize = size; [self layoutAlertControllerView]; [self layoutChildViews]; } #pragma mark - Private - (instancetype)initWithTitle:(nullable NSString *)title message:(nullable NSString *)message customAlertView:(UIView *)customAlertView customHeaderView:(UIView *)customHeaderView customActionSequenceView:(UIView *)customActionSequenceView componentView:(UIView *)componentView preferredStyle:(SPAlertControllerStyle)preferredStyle animationType:(SPAlertAnimationType)animationType { self = [self init]; _title = title; _message = message; _preferredStyle = preferredStyle; // 如果是默认动画,preferredStyle为alert时动画默认为alpha,preferredStyle为actionShee时动画默认为fromBottom if (animationType == SPAlertAnimationTypeDefault) { if (preferredStyle == SPAlertControllerStyleAlert) { animationType = SPAlertAnimationTypeShrink; } else { animationType = SPAlertAnimationTypeFromBottom; } } _animationType = animationType; if (preferredStyle == SPAlertControllerStyleAlert) { _maxMarginForAlert = (MIN(SP_SCREEN_WIDTH, SP_SCREEN_HEIGHT) - 275) / 2.0; _minDistanceToEdges = (MIN(SP_SCREEN_WIDTH, SP_SCREEN_HEIGHT) - 275) / 2.0; _cornerRadius = 6.0; } else { _minDistanceToEdges = 70; _maxTopMarginForActionSheet = 70; _cornerRadius = 13.0; } if (preferredStyle == SPAlertControllerStyleAlert) { _actionAxis = UILayoutConstraintAxisHorizontal; } else { _actionAxis = UILayoutConstraintAxisVertical; } _customAlertView = customAlertView; _customHeaderView = customHeaderView; _customActionSequenceView = customActionSequenceView; _componentView = componentView; // componentView参数是为了支持老版本的自定义footerView return self; } - (instancetype)init { if (self = [super init]) { [self initialize]; } return self; } - (void)initialize { // 视图控制器定义它呈现视图控制器的过渡风格(默认为NO) self.providesPresentationContextTransitionStyle = YES; self.definesPresentationContext = YES; self.modalPresentationStyle = UIModalPresentationCustom; self.transitioningDelegate = self; _titleFont = [UIFont boldSystemFontOfSize:18]; _titleColor = [SPColorStyle lightBlack_DarkWhiteColor]; _messageFont = [UIFont systemFontOfSize:16]; _messageColor = [SPColorStyle grayColor]; _textAlignment = NSTextAlignmentCenter; _imageLimitSize = CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX); _cornerRadiusForAlert = 6.0; _backgroundViewAppearanceStyle = -1; _backgroundViewAlpha = 0.5; _tapBackgroundViewDismiss = YES; _needDialogBlur = NO; _maxNumberOfActionHorizontalArrangementForAlert = 2; } - (void)layoutAlertControllerView { if (!self.alertControllerView.superview) return; if (self.alertControllerViewConstraints) { [NSLayoutConstraint deactivateConstraints:self.alertControllerViewConstraints]; self.alertControllerViewConstraints = nil; } if (self.preferredStyle == SPAlertControllerStyleAlert) { // alert样式 [self layoutAlertControllerViewForAlertStyle]; } else { // actionSheet样式 [self layoutAlertControllerViewForActionSheetStyle]; } } - (void)layoutAlertControllerViewForAlertStyle { UIView *alertControllerView = self.alertControllerView; NSMutableArray *alertControllerViewConstraints = [NSMutableArray array]; CGFloat topValue = _minDistanceToEdges; CGFloat bottomValue = _minDistanceToEdges; CGFloat maxWidth = MIN(SP_SCREEN_WIDTH, SP_SCREEN_HEIGHT)-_minDistanceToEdges * 2; CGFloat maxHeight = SP_SCREEN_HEIGHT-topValue-bottomValue; if (!self.customAlertView) { // 当屏幕旋转的时候,为了保持alert样式下的宽高不变,因此取MIN(SP_SCREEN_WIDTH, SP_SCREEN_HEIGHT) [alertControllerViewConstraints addObject:[NSLayoutConstraint constraintWithItem:alertControllerView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:maxWidth]]; } else { [alertControllerViewConstraints addObject:[NSLayoutConstraint constraintWithItem:alertControllerView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationLessThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:maxWidth]]; if (_customViewSize.width) { // 如果宽度没有值,则会假定customAlertView水平方向能由子控件撑起 // 限制最大宽度,且能保证内部约束不报警告 CGFloat customWidth = MIN(_customViewSize.width, maxWidth); [alertControllerViewConstraints addObject:[NSLayoutConstraint constraintWithItem:alertControllerView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:customWidth]]; } if (_customViewSize.height) { // 如果高度没有值,则会假定customAlertView垂直方向能由子控件撑起 CGFloat customHeight = MIN(_customViewSize.height, maxHeight); [alertControllerViewConstraints addObject:[NSLayoutConstraint constraintWithItem:alertControllerView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:customHeight]]; } } NSLayoutConstraint *topConstraint = [NSLayoutConstraint constraintWithItem:alertControllerView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:alertControllerView.superview attribute:NSLayoutAttributeTop multiplier:1.0f constant:topValue]; topConstraint.priority = 999.0;// 这里优先级为999.0是为了小于垂直中心的优先级,如果含有文本输入框,键盘弹出后,特别是旋转到横屏后,对话框的空间比较小,这个时候优先偏移垂直中心,顶部优先级按理说应该会被忽略,但是由于子控件含有scrollView,所以该优先级仍然会被激活,子控件显示不全scrollView可以滑动。如果外界自定义了整个对话框,且自定义的view上含有文本输入框,子控件不含有scrollView,顶部间距会被忽略 [alertControllerViewConstraints addObject:topConstraint]; NSLayoutConstraint *bottomConstraint = [NSLayoutConstraint constraintWithItem:alertControllerView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationLessThanOrEqual toItem:alertControllerView.superview attribute:NSLayoutAttributeBottom multiplier:1.0f constant:-bottomValue]; bottomConstraint.priority = 999.0; // 优先级跟顶部同理 [alertControllerViewConstraints addObject:bottomConstraint]; [alertControllerViewConstraints addObject:[NSLayoutConstraint constraintWithItem:alertControllerView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:alertControllerView.superview attribute:NSLayoutAttributeCenterX multiplier:1.0 constant: _offsetForAlert.x]]; NSLayoutConstraint *alertControllerViewConstraintCenterY = [NSLayoutConstraint constraintWithItem:alertControllerView attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:alertControllerView.superview attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:(self.isBeingPresented && !self.isBeingDismissed) ? 0 : _offsetForAlert.y]; [alertControllerViewConstraints addObject:alertControllerViewConstraintCenterY]; [NSLayoutConstraint activateConstraints:alertControllerViewConstraints]; self.alertControllerViewConstraints = alertControllerViewConstraints; } - (void)layoutAlertControllerViewForActionSheetStyle { switch (self.animationType) { case SPAlertAnimationTypeFromBottom: case SPAlertAnimationTypeRaiseUp: default: [self layoutAlertControllerViewForAnimationTypeWithHV:@"H" equalAttribute:NSLayoutAttributeBottom notEqualAttribute:NSLayoutAttributeTop lessOrGreaterRelation:NSLayoutRelationGreaterThanOrEqual]; break; case SPAlertAnimationTypeFromTop: case SPAlertAnimationTypeDropDown: [self layoutAlertControllerViewForAnimationTypeWithHV:@"H" equalAttribute:NSLayoutAttributeTop notEqualAttribute:NSLayoutAttributeBottom lessOrGreaterRelation:NSLayoutRelationLessThanOrEqual]; break; case SPAlertAnimationTypeFromLeft: [self layoutAlertControllerViewForAnimationTypeWithHV:@"V" equalAttribute:NSLayoutAttributeLeft notEqualAttribute:NSLayoutAttributeRight lessOrGreaterRelation:NSLayoutRelationLessThanOrEqual]; break; case SPAlertAnimationTypeFromRight: [self layoutAlertControllerViewForAnimationTypeWithHV:@"V" equalAttribute:NSLayoutAttributeRight notEqualAttribute:NSLayoutAttributeLeft lessOrGreaterRelation:NSLayoutRelationLessThanOrEqual]; break; } } - (void)layoutAlertControllerViewForAnimationTypeWithHV:(NSString *)hv equalAttribute:(NSLayoutAttribute)equalAttribute notEqualAttribute:(NSLayoutAttribute)notEqualAttribute lessOrGreaterRelation:(NSLayoutRelation)relation { UIView *alertControllerView = self.alertControllerView; NSMutableArray *alertControllerViewConstraints = [NSMutableArray array]; if (!self.customAlertView) { [alertControllerViewConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:[NSString stringWithFormat:@"%@:|-0-[alertControllerView]-0-|",hv] options:0 metrics:nil views:NSDictionaryOfVariableBindings(alertControllerView)]]; } else { NSLayoutAttribute centerXorY = [hv isEqualToString:@"H"] ? NSLayoutAttributeCenterX : NSLayoutAttributeCenterY; [alertControllerViewConstraints addObject:[NSLayoutConstraint constraintWithItem:alertControllerView attribute:centerXorY relatedBy:NSLayoutRelationEqual toItem:alertControllerView.superview attribute:centerXorY multiplier:1.0 constant:0]]; if (_customViewSize.width) { // 如果宽度没有值,则会假定customAlertViewh水平方向能由子控件撑起 CGFloat alertControllerViewWidth = 0.0; if ([hv isEqualToString:@"H"]) { alertControllerViewWidth = MIN(_customViewSize.width, SP_SCREEN_WIDTH); } else { alertControllerViewWidth = MIN(_customViewSize.width, SP_SCREEN_WIDTH-_minDistanceToEdges); } [alertControllerViewConstraints addObject:[NSLayoutConstraint constraintWithItem:alertControllerView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:alertControllerViewWidth]]; } if (_customViewSize.height) { // 如果高度没有值,则会假定customAlertViewh垂直方向能由子控件撑起 CGFloat alertControllerViewHeight = 0.0; if ([hv isEqualToString:@"H"]) { alertControllerViewHeight = MIN(_customViewSize.height, SP_SCREEN_HEIGHT-_minDistanceToEdges); } else { alertControllerViewHeight = MIN(_customViewSize.height, SP_SCREEN_HEIGHT); } [alertControllerViewConstraints addObject:[NSLayoutConstraint constraintWithItem:alertControllerView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:alertControllerViewHeight]]; } } [alertControllerViewConstraints addObject:[NSLayoutConstraint constraintWithItem:alertControllerView attribute:equalAttribute relatedBy:NSLayoutRelationEqual toItem:alertControllerView.superview attribute:equalAttribute multiplier:1.0 constant:0]]; NSLayoutConstraint *someSideConstraint = [NSLayoutConstraint constraintWithItem:alertControllerView attribute:notEqualAttribute relatedBy:relation toItem:alertControllerView.superview attribute:notEqualAttribute multiplier:1.0 constant:_minDistanceToEdges]; someSideConstraint.priority = 999.0; [alertControllerViewConstraints addObject:someSideConstraint]; [NSLayoutConstraint activateConstraints:alertControllerViewConstraints]; self.alertControllerViewConstraints = alertControllerViewConstraints; } - (void)layoutChildViews { // 对头部布局 [self layoutHeaderView]; // 对头部和action部分之间的分割线布局 [self layoutHeaderActionLine]; // 对组件view布局 [self layoutComponentView]; // 对组件view与action部分之间的分割线布局 [self layoutComponentActionLine]; // 对action部分布局 [self layoutActionSequenceView]; } // 对头部布局,高度由子控件撑起 - (void)layoutHeaderView { UIView *headerView = self.customHeaderView ? self.customHeaderView : self.headerView; if (!headerView.superview) return; UIView *alertView = self.alertView; NSMutableArray *headerViewConstraints = [NSMutableArray array]; if (self.headerViewConstraints) { [NSLayoutConstraint deactivateConstraints:self.headerViewConstraints]; self.headerViewConstraints = nil; } if (!self.customHeaderView) { [headerViewConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[headerView]-0-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(headerView)]]; } else { if (_customViewSize.width) { CGFloat maxWidth = [self maxWidth]; CGFloat headerViewWidth = MIN(maxWidth, _customViewSize.width); [headerViewConstraints addObject:[NSLayoutConstraint constraintWithItem:headerView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:headerViewWidth]]; } if (_customViewSize.height) { NSLayoutConstraint *customHeightConstraint = [NSLayoutConstraint constraintWithItem:headerView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:_customViewSize.height]; customHeightConstraint.priority = UILayoutPriorityDefaultHigh; [headerViewConstraints addObject:customHeightConstraint]; } [headerViewConstraints addObject:[NSLayoutConstraint constraintWithItem:headerView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:alertView attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0]]; } [headerViewConstraints addObject:[NSLayoutConstraint constraintWithItem:headerView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:alertView attribute:NSLayoutAttributeTop multiplier:1.0 constant:0]]; if (!self.headerActionLine.superview) { [headerViewConstraints addObject:[NSLayoutConstraint constraintWithItem:headerView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:alertView attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0]]; } [NSLayoutConstraint activateConstraints:headerViewConstraints]; self.headerViewConstraints = headerViewConstraints; } // 对头部和action部分之间的分割线布局 - (void)layoutHeaderActionLine { if (!self.headerActionLine.superview) return; UIView *headerActionLine = self.headerActionLine; UIView *headerView = self.customHeaderView ? self.customHeaderView : self.headerView; UIView *actionSequenceView = self.customActionSequenceView ? self.customActionSequenceView : self.actionSequenceView; NSMutableArray *headerActionLineConstraints = [NSMutableArray array]; if (self.headerActionLineConstraints) { [NSLayoutConstraint deactivateConstraints:self.headerActionLineConstraints]; self.headerActionLineConstraints = nil; } [headerActionLineConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[headerActionLine]-0-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(headerActionLine)]]; [headerActionLineConstraints addObject:[NSLayoutConstraint constraintWithItem:headerActionLine attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:headerView attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0]]; if (!self.componentView.superview) { [headerActionLineConstraints addObject:[NSLayoutConstraint constraintWithItem:headerActionLine attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:actionSequenceView attribute:NSLayoutAttributeTop multiplier:1.0 constant:0]]; } [headerActionLineConstraints addObject:[NSLayoutConstraint constraintWithItem:headerActionLine attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:SP_LINE_WIDTH]]; [NSLayoutConstraint activateConstraints:headerActionLineConstraints]; self.headerActionLineConstraints = headerActionLineConstraints; } // 对组件view布局 - (void)layoutComponentView { if (!self.componentView.superview) return; UIView *componentView = self.componentView; UIView *headerActionLine = self.headerActionLine; UIView *componentActionLine = self.componentActionLine; NSMutableArray *componentViewConstraints = [NSMutableArray array]; if (self.componentViewConstraints) { [NSLayoutConstraint deactivateConstraints:self.componentViewConstraints]; self.componentViewConstraints = nil; } [componentViewConstraints addObject:[NSLayoutConstraint constraintWithItem:componentView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:headerActionLine attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0]]; [componentViewConstraints addObject:[NSLayoutConstraint constraintWithItem:componentView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:componentActionLine attribute:NSLayoutAttributeTop multiplier:1.0 constant:0]]; [componentViewConstraints addObject:[NSLayoutConstraint constraintWithItem:componentView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.alertView attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0]]; if (_customViewSize.height) { NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:componentView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:_customViewSize.height]; heightConstraint.priority = UILayoutPriorityDefaultHigh; // 750 [componentViewConstraints addObject:heightConstraint]; } if (_customViewSize.width) { CGFloat maxWidth = [self maxWidth]; CGFloat componentViewWidth = MIN(maxWidth, _customViewSize.width); [componentViewConstraints addObject:[NSLayoutConstraint constraintWithItem:componentView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:componentViewWidth]]; } [NSLayoutConstraint activateConstraints:componentViewConstraints]; self.componentViewConstraints = componentViewConstraints; } // 对组件view和action部分之间的分割线布局 - (void)layoutComponentActionLine { if (!self.componentActionLine.superview) return; UIView *componentActionLine = self.componentActionLine; NSMutableArray *componentActionLineConstraints = [NSMutableArray array]; if (self.componentActionLineConstraints) { [NSLayoutConstraint deactivateConstraints:self.componentActionLineConstraints]; self.componentActionLineConstraints = nil; } [componentActionLineConstraints addObject:[NSLayoutConstraint constraintWithItem:componentActionLine attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.actionSequenceView attribute:NSLayoutAttributeTop multiplier:1.0 constant:0]]; [componentActionLineConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[componentActionLine]-0-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(componentActionLine)]]; [componentActionLineConstraints addObject:[NSLayoutConstraint constraintWithItem:componentActionLine attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:SP_LINE_WIDTH]]; [NSLayoutConstraint activateConstraints:componentActionLineConstraints]; self.componentActionLineConstraints = componentActionLineConstraints; } // 对action部分布局,高度由子控件撑起 - (void)layoutActionSequenceView { UIView *actionSequenceView = self.customActionSequenceView ? self.customActionSequenceView : self.actionSequenceView; if (!actionSequenceView.superview) return; UIView *alertView = self.alertView; UIView *headerActionLine = self.headerActionLine; NSMutableArray *actionSequenceViewConstraints = [NSMutableArray array]; if (self.actionSequenceViewConstraints) { [NSLayoutConstraint deactivateConstraints:self.actionSequenceViewConstraints]; self.actionSequenceViewConstraints = nil; } if (!self.customActionSequenceView) { [actionSequenceViewConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[actionSequenceView]-0-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(actionSequenceView)]]; } else { if (_customViewSize.width) { CGFloat maxWidth = [self maxWidth]; if (_customViewSize.width > maxWidth) _customViewSize.width = maxWidth; [actionSequenceViewConstraints addObject:[NSLayoutConstraint constraintWithItem:actionSequenceView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:_customViewSize.width]]; } if (_customViewSize.height) { NSLayoutConstraint *customHeightConstraint = [NSLayoutConstraint constraintWithItem:actionSequenceView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:_customViewSize.height]; customHeightConstraint.priority = UILayoutPriorityDefaultHigh; [actionSequenceViewConstraints addObject:customHeightConstraint]; } [actionSequenceViewConstraints addObject:[NSLayoutConstraint constraintWithItem:actionSequenceView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:alertView attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0]]; } if (!headerActionLine) { [actionSequenceViewConstraints addObject:[NSLayoutConstraint constraintWithItem:actionSequenceView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:alertView attribute:NSLayoutAttributeTop multiplier:1.0 constant:0]]; } [actionSequenceViewConstraints addObject:[NSLayoutConstraint constraintWithItem:actionSequenceView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:alertView attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0]]; [NSLayoutConstraint activateConstraints:actionSequenceViewConstraints]; self.actionSequenceViewConstraints = actionSequenceViewConstraints; } - (CGFloat)maxWidth { if (self.preferredStyle == SPAlertControllerStyleAlert) { return MIN(SP_SCREEN_WIDTH, SP_SCREEN_HEIGHT)-_minDistanceToEdges * 2; } else { return SP_SCREEN_WIDTH; } } // 文字显示不全处理 - (void)handleIncompleteTextDisplay { // alert样式下水平排列时如果文字显示不全则垂直排列 if (!self.isForceLayout) { // 外界没有设置排列方式 if (self.preferredStyle == SPAlertControllerStyleAlert) { for (SPAlertAction *action in self.actions) { // 预估按钮宽度 CGFloat preButtonWidth = (MIN(SP_SCREEN_WIDTH, SP_SCREEN_HEIGHT) - _minDistanceToEdges * 2 - SP_LINE_WIDTH * (self.actions.count - 1)) / self.actions.count - action.titleEdgeInsets.left - action.titleEdgeInsets.right; // 如果action的标题文字总宽度,大于按钮的contentRect的宽度,则说明水平排列会导致文字显示不全,此时垂直排列 if (action.attributedTitle) { if (ceil([action.attributedTitle boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, SP_ACTION_HEIGHT) options:NSStringDrawingUsesLineFragmentOrigin context:nil].size.width) > preButtonWidth) { _actionAxis = UILayoutConstraintAxisVertical; [self updateActionAxis]; [self.actionSequenceView setNeedsUpdateConstraints]; break; // 一定要break,只要有一个按钮文字过长就垂直排列 } } else { if (ceil([action.title boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, SP_ACTION_HEIGHT) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:action.titleFont} context:nil].size.width) > preButtonWidth) { _actionAxis = UILayoutConstraintAxisVertical; [self updateActionAxis]; [self.actionSequenceView setNeedsUpdateConstraints]; break; } } } } } } // 专门处理第三方IQKeyboardManager,非自定义view时禁用IQKeyboardManager移动textView/textField效果,自定义view时取消禁用 - (void)handleIQKeyboardManager { SEL selector = NSSelectorFromString(@"sharedManager"); IMP imp = [NSClassFromString(@"IQKeyboardManager") methodForSelector:selector]; if (imp != NULL) { NSObject *(*func)(id, SEL) = (void *)imp; NSObject *mgr = func(NSClassFromString(@"IQKeyboardManager"), selector); if ([mgr isKindOfClass:NSClassFromString(@"IQKeyboardManager")]) { @try { NSMutableSet *disabledDistanceHandlingClasses = [mgr valueForKey:@"_disabledDistanceHandlingClasses"]; NSMutableSet *disabledToolbarClasses = [mgr valueForKey:@"_disabledToolbarClasses"]; if (![disabledDistanceHandlingClasses containsObject:NSClassFromString(@"SPAlertController")]) { [disabledDistanceHandlingClasses addObject:NSClassFromString(@"SPAlertController")]; [disabledToolbarClasses addObject:NSClassFromString(@"SPAlertController")]; } } @catch (NSException *exception) { NSLog(@"exception = %@",exception); } @finally { } } } } - (void)configureHeaderView { if (self.image) { self.headerView.imageLimitSize = _imageLimitSize; self.headerView.imageView.image = _image; self.headerView.imageView.tintColor = _imageTintColor; [self.headerView setNeedsUpdateConstraints]; } if(self.attributedTitle.length) { self.headerView.titleLabel.attributedText = self.attributedTitle; [self setupPreferredMaxLayoutWidthForLabel:self.headerView.titleLabel]; } else if(self.title.length) { self.headerView.titleLabel.text = _title; self.headerView.titleLabel.font = _titleFont; self.headerView.titleLabel.textColor = _titleColor; self.headerView.titleLabel.textAlignment = _textAlignment; [self setupPreferredMaxLayoutWidthForLabel:self.headerView.titleLabel]; } if (self.attributedMessage.length) { self.headerView.messageLabel.attributedText = self.attributedMessage; [self setupPreferredMaxLayoutWidthForLabel:self.headerView.messageLabel]; } else if (self.message.length) { self.headerView.messageLabel.text = _message; self.headerView.messageLabel.font = _messageFont; self.headerView.messageLabel.textColor = _messageColor; self.headerView.messageLabel.textAlignment = _textAlignment; [self setupPreferredMaxLayoutWidthForLabel:self.headerView.messageLabel]; } } - (void)setupPreferredMaxLayoutWidthForLabel:(UILabel *)textLabel { if (self.preferredStyle == SPAlertControllerStyleAlert) { textLabel.preferredMaxLayoutWidth = MIN(SP_SCREEN_WIDTH, SP_SCREEN_HEIGHT) - self.minDistanceToEdges * 2 - self.headerView.contentEdgeInsets.left - self.headerView.contentEdgeInsets.right; } else { textLabel.preferredMaxLayoutWidth = SP_SCREEN_WIDTH - self.headerView.contentEdgeInsets.left - self.headerView.contentEdgeInsets.right; } } // 这个方法是实现点击回车切换到下一个textField,如果没有下一个,会自动退出键盘. 不能在代理方法里实现,因为如果设置了代理,外界就不能成为textFiled的代理了,通知也监听不到回车 - (void)textFieldDidEndOnExit:(UITextField *)textField { NSInteger index = [self.textFields indexOfObject:textField]; if (self.textFields.count > index + 1) { UITextField *nextTextField = [self.textFields objectAtIndex:index + 1]; [textField resignFirstResponder]; [nextTextField becomeFirstResponder]; } } // 更新action的排列方式 - (void)updateActionAxis { self.actionSequenceView.axis = _actionAxis; if (_actionAxis == UILayoutConstraintAxisVertical) { self.actionSequenceView.stackViewDistribution = UIStackViewDistributionFillProportionally;// 布局方式为子控件自适应内容高度 } else { self.actionSequenceView.stackViewDistribution = UIStackViewDistributionFillEqually; // 布局方式为子控件等宽 } } // 该方法是保证被废弃的maxNumberOfActionHorizontalArrangementForAlert属性的有效性 - (void)setupActionAxis { if (self.preferredStyle == SPAlertControllerStyleAlert) { if (self.actions.count > self.maxNumberOfActionHorizontalArrangementForAlert) { _actionAxis = UILayoutConstraintAxisVertical; [self updateActionAxis]; } else { _actionAxis = UILayoutConstraintAxisHorizontal; [self updateActionAxis]; } } } - (void)makeViewOffsetWithAnimated:(BOOL)animated { if (!self.beingPresented && !self.beingDismissed) { [self layoutAlertControllerView]; if (animated) { [UIView animateWithDuration:0.25 animations:^{ [self.view.superview layoutIfNeeded]; }]; } } } // 获取自定义view的大小 - (CGSize)sizeForCustomView:(UIView *)customView { [customView layoutIfNeeded]; CGSize settingSize = customView.frame.size; CGSize fittingSize = [customView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize]; return CGSizeMake(MAX(settingSize.width, fittingSize.width), MAX(settingSize.height, fittingSize.height)); } #pragma mark - system methods - (void)loadView { // 重新创建self.view,这样可以采用自己的一套布局,轻松改变控制器view的大小 self.view = self.alertControllerView; } - (void)viewDidLoad { [super viewDidLoad]; [self configureHeaderView]; self.needDialogBlur = _needDialogBlur; self.automaticallyAdjustsScrollViewInsets = NO; } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; [self handleIQKeyboardManager]; if (!_isForceOffset && !_customAlertView && !_customHeaderView && !_customActionSequenceView && !_componentView) { // 监听键盘改变frame,键盘frame改变需要移动对话框 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardFrameWillChange:) name:UIKeyboardWillChangeFrameNotification object:nil]; } if (self.textFields.count) { UITextField *firstTextfield = [self.textFields firstObject]; if (!firstTextfield.isFirstResponder) { [firstTextfield becomeFirstResponder]; } } } - (void)viewWillLayoutSubviews { [super viewWillLayoutSubviews]; // 屏幕旋转后宽高发生了交换,头部的label最大宽度需要重新计算 [self setupPreferredMaxLayoutWidthForLabel:self.headerView.titleLabel]; [self setupPreferredMaxLayoutWidthForLabel:self.headerView.messageLabel]; // 对自己创建的alertControllerView布局,在这个方法里,self.view才有父视图,有父视图才能改变其约束 [self layoutAlertControllerView]; [self layoutChildViews]; if (self.preferredStyle == SPAlertControllerStyleActionSheet) { [self setCornerRadius:_cornerRadius]; } } - (void)viewDidLayoutSubviews { [super viewDidLayoutSubviews]; [self handleIncompleteTextDisplay]; } - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; } - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; } - (void)viewDidDisappear:(BOOL)animated { [super viewDidDisappear:animated]; } - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; } #pragma mark - 键盘通知 - (void)keyboardFrameWillChange:(NSNotification *)notification { if (!_isForceOffset && (_offsetForAlert.y == 0.0 || _textFields.lastObject.isFirstResponder)) { CGRect keyboardEndFrame = [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue]; CGFloat keyboardEndY = keyboardEndFrame.origin.y; CGFloat diff = fabs((SP_SCREEN_HEIGHT - keyboardEndY) * 0.5); _offsetForAlert.y = -diff; [self makeViewOffsetWithAnimated:YES]; } } #pragma mark - setterx - (void)setTitle:(NSString *)title { _title = title; if (self.isViewLoaded) { // 如果条件为真,说明外界在对title赋值之前就已经使用了self.view,先走了viewDidLoad方法,如果先走的viewDidLoad,需要在title的setter方法中重新设置数据,以下setter方法中的条件同理 self.headerView.titleLabel.text = title; // 文字发生变化后再更新布局,这里更新布局也不是那么重要,因为headerView中的布局方法只有当SPAlertController被present后才会走一次,而那时候,一般title,titleFont、message、messageFont等都是最新值,这里防止的是:在SPAlertController被present后的某个时刻再去设置title,titleFont等,我们要更新布局 if (self.presentationController.presentingViewController) { // 这个if条件的意思是当SPAlertController被present后的某个时刻设置了title,如果在present之前设置的就不用更新,系统会主动更新 [self.headerView setNeedsUpdateConstraints]; } } } - (void)setTitleFont:(UIFont *)titleFont { _titleFont = titleFont; if (self.isViewLoaded) { self.headerView.titleLabel.font = titleFont; if (self.presentationController.presentingViewController) { [self.headerView setNeedsUpdateConstraints]; } } } - (void)setTitleColor:(UIColor *)titleColor { _titleColor = titleColor; if (self.isViewLoaded) { self.headerView.titleLabel.textColor = titleColor; } } - (void)setMessage:(NSString *)message { _message = message; if (self.isViewLoaded) { self.headerView.messageLabel.text = message; if (self.presentationController.presentingViewController) { [self.headerView setNeedsUpdateConstraints]; } } } - (void)setMessageFont:(UIFont *)messageFont { _messageFont = messageFont; if (self.isViewLoaded) { self.headerView.messageLabel.font = messageFont; if (self.presentationController.presentingViewController) { [self.headerView setNeedsUpdateConstraints]; } } } - (void)setMessageColor:(UIColor *)messageColor { _messageColor = messageColor; if (self.isViewLoaded) { self.headerView.messageLabel.textColor = messageColor; } } - (void)setTextAlignment:(NSTextAlignment)textAlignment { _textAlignment = textAlignment; self.headerView.titleLabel.textAlignment = _textAlignment; self.headerView.messageLabel.textAlignment = _textAlignment; } - (void)setIcon:(UIImage *)image { _image = image; if (self.isViewLoaded) { self.headerView.imageView.image = _image; if (self.presentationController.presentingViewController) { [self.headerView setNeedsUpdateConstraints]; } } } - (void)setIconLimitSize:(CGSize)imageLimitSize { _imageLimitSize = imageLimitSize; if (self.isViewLoaded) { self.headerView.imageLimitSize = _imageLimitSize; if (self.presentationController.presentingViewController) { [self.headerView setNeedsUpdateConstraints]; } } } - (void)setImageTintColor:(UIColor *)imageTintColor { _imageTintColor = imageTintColor; if (self.isViewLoaded) { self.headerView.imageView.tintColor = imageTintColor; } } - (void)setAttributedTitle:(NSAttributedString *)attributedTitle { _attributedTitle = attributedTitle; if (self.isViewLoaded) { self.headerView.titleLabel.attributedText = _attributedTitle; if (self.presentationController.presentingViewController) { [self.headerView setNeedsUpdateConstraints]; } } } - (void)setAttributedMessage:(NSAttributedString *)attributedMessage { _attributedMessage = attributedMessage; if (self.isViewLoaded) { self.headerView.messageLabel.attributedText = _attributedMessage; if (self.presentationController.presentingViewController) { [self.headerView setNeedsUpdateConstraints]; } } } // 该属性3.0版本开始被废弃 - (void)setMaxMarginForAlert:(CGFloat)maxMarginForAlert { _maxMarginForAlert = maxMarginForAlert; self.minDistanceToEdges = _maxMarginForAlert; } // 该属性3.0版本开始被废弃 - (void)setMaxTopMarginForActionSheet:(CGFloat)maxTopMarginForActionSheet { _maxTopMarginForActionSheet = maxTopMarginForActionSheet; self.minDistanceToEdges = _maxTopMarginForActionSheet; } - (void)setMinDistanceToEdges:(CGFloat)minDistanceToEdges { _minDistanceToEdges = minDistanceToEdges; if (self.isViewLoaded) { [self setupPreferredMaxLayoutWidthForLabel:self.headerView.titleLabel]; [self setupPreferredMaxLayoutWidthForLabel:self.headerView.messageLabel]; if (self.presentationController.presentingViewController) { [self layoutAlertControllerView]; [self.headerView setNeedsUpdateConstraints]; [self.actionSequenceView setNeedsUpdateConstraints]; } } } - (void)setCornerRadius:(CGFloat)cornerRadius { _cornerRadius = cornerRadius; if (self.preferredStyle == SPAlertControllerStyleAlert) { self.containerView.layer.cornerRadius = _cornerRadius; self.containerView.layer.masksToBounds = YES; } else { if (_cornerRadius > 0.0) { UIRectCorner corner = UIRectCornerTopLeft | UIRectCornerTopRight; switch (_animationType) { case SPAlertAnimationTypeFromBottom: corner = UIRectCornerTopLeft | UIRectCornerTopRight; break; case SPAlertAnimationTypeFromTop: corner = UIRectCornerBottomLeft | UIRectCornerBottomRight; break; case SPAlertAnimationTypeFromLeft: corner = UIRectCornerTopRight | UIRectCornerBottomRight; break; case SPAlertAnimationTypeFromRight: corner = UIRectCornerTopLeft | UIRectCornerBottomLeft; break; default: break; } CAShapeLayer *maskLayer = (CAShapeLayer *)_containerView.layer.mask; maskLayer.path = [UIBezierPath bezierPathWithRoundedRect:_containerView.bounds byRoundingCorners:corner cornerRadii:CGSizeMake(_cornerRadius, _cornerRadius)].CGPath; maskLayer.frame = _containerView.bounds; } else { _containerView.layer.mask = nil; } } } - (void)setCornerRadiusForAlert:(CGFloat)cornerRadiusForAlert { _cornerRadiusForAlert = cornerRadiusForAlert; _cornerRadius = cornerRadiusForAlert; if (self.preferredStyle == SPAlertControllerStyleAlert) { self.containerView.layer.cornerRadius = _cornerRadiusForAlert; self.containerView.layer.masksToBounds = YES; } } // 此属性3.0版本开始被废弃 - (void)setMaxNumberOfActionHorizontalArrangementForAlert:(NSInteger)maxNumberOfActionHorizontalArrangementForAlert { _maxNumberOfActionHorizontalArrangementForAlert = maxNumberOfActionHorizontalArrangementForAlert; // 被废弃的maxNumberOfActionHorizontalArrangementForAlert属性需要的方法 [self setupActionAxis]; } - (void)setActionAxis:(UILayoutConstraintAxis)actionAxis { _actionAxis = actionAxis; // 调用该setter方法则认为是强制布局,该setter方法只有外界能调,这样才能判断外界有没有调用actionAxis的setter方法,从而是否按照外界的指定布局方式进行布局 _isForceLayout = YES; [self updateActionAxis]; } - (void)setOffsetForAlert:(CGPoint)offsetForAlert { _offsetForAlert = offsetForAlert; _isForceOffset = YES; [self makeViewOffsetWithAnimated:NO]; } // 被废弃 - (void)setOffsetYForAlert:(CGFloat)offsetYForAlert { _offsetYForAlert = offsetYForAlert; _offsetForAlert.y = _offsetYForAlert; _isForceOffset = YES; } - (void)setNeedDialogBlur:(BOOL)needDialogBlur { _needDialogBlur = needDialogBlur; if (_needDialogBlur) { self.containerView.backgroundColor = [UIColor clearColor]; if (!self.dimmingKnockoutBackdropView) { self.dimmingKnockoutBackdropView = [NSClassFromString(@"_UIDimmingKnockoutBackdropView") alloc]; if (self.dimmingKnockoutBackdropView) { // 下面4行相当于self.dimmingKnockoutBackdropView = [self.dimmingKnockoutBackdropView performSelector:NSSelectorFromString(@"initWithStyle:") withObject:@(UIBlurEffectStyleLight)]; SEL selector = NSSelectorFromString(@"initWithStyle:"); IMP imp = [self.dimmingKnockoutBackdropView methodForSelector:selector]; if (imp != NULL) { UIView *(*func)(id, SEL,UIBlurEffectStyle) = (void *)imp; self.dimmingKnockoutBackdropView = func(self.dimmingKnockoutBackdropView, selector, UIBlurEffectStyleLight); self.dimmingKnockoutBackdropView.frame = self.containerView.bounds; self.dimmingKnockoutBackdropView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; [self.containerView insertSubview:self.dimmingKnockoutBackdropView atIndex:0]; } } else { // 这个else是防止假如_UIDimmingKnockoutBackdropView这个类不存在了的时候,做一个备案 UIBlurEffect *blur = [UIBlurEffect effectWithStyle:UIBlurEffectStyleExtraLight]; self.dimmingKnockoutBackdropView = [[UIVisualEffectView alloc] initWithEffect:blur]; self.dimmingKnockoutBackdropView.frame = self.containerView.bounds; self.dimmingKnockoutBackdropView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; [self.containerView insertSubview:self.dimmingKnockoutBackdropView atIndex:0]; } } } else { [self.dimmingKnockoutBackdropView removeFromSuperview]; self.dimmingKnockoutBackdropView = nil; if (_customAlertView) { self.containerView.backgroundColor = [UIColor clearColor]; } else { self.containerView.backgroundColor = [SPColorStyle lightWhite_DarkBlackColor]; } } } #pragma mark - lazy load - (UIView *)alertControllerView { if (!_alertControllerView) { UIView *alertControllerView = [[UIView alloc] init]; alertControllerView.translatesAutoresizingMaskIntoConstraints = NO; _alertControllerView = alertControllerView; } return _alertControllerView; } - (UIView *)containerView { if (!_containerView) { UIView *containerView = [[UIView alloc] init]; containerView.frame = self.alertControllerView.bounds; containerView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; if (_preferredStyle == SPAlertControllerStyleAlert) { containerView.layer.cornerRadius = _cornerRadius; containerView.layer.masksToBounds = YES; } else { if (_cornerRadius > 0.0) { CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init]; containerView.layer.mask = maskLayer; } } [self.alertControllerView addSubview:containerView]; _containerView = containerView; } return _containerView; } - (UIView *)alertView { if (!_alertView) { UIView *alertView = [[UIView alloc] init]; alertView.frame = self.alertControllerView.bounds; alertView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; if (!self.customAlertView) { [self.containerView addSubview:alertView]; } _alertView = alertView; } return _alertView; } - (UIView *)customAlertView { // customAlertView有值但是没有父view if (_customAlertView && !_customAlertView.superview) { if (CGSizeEqualToSize(_customViewSize, CGSizeZero)) { // 获取_customAlertView的大小 _customViewSize = [self sizeForCustomView:_customAlertView]; } // 必须在在下面2行代码之前获取_customViewSize _customAlertView.frame = self.alertControllerView.bounds; _customAlertView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; [self.containerView addSubview:_customAlertView]; } return _customAlertView; } - (SPInterfaceHeaderScrollView *)headerView { if (!_headerView) { SPInterfaceHeaderScrollView *headerView = [[SPInterfaceHeaderScrollView alloc] init]; headerView.backgroundColor = [SPColorStyle normalColor]; headerView.translatesAutoresizingMaskIntoConstraints = NO; __weak typeof(self) weakSelf = self; headerView.headerViewSfeAreaDidChangBlock = ^{ [weakSelf setupPreferredMaxLayoutWidthForLabel:weakSelf.headerView.titleLabel]; [weakSelf setupPreferredMaxLayoutWidthForLabel:weakSelf.headerView.messageLabel]; }; if (!self.customHeaderView) { if ((self.title.length || self.attributedTitle.length || self.message.length || self.attributedMessage.length || self.textFields.count || self.image)) { [self.alertView addSubview:headerView]; } } _headerView = headerView; } return _headerView; } - (UIView *)customHeaderView { // _customHeaderView有值但是没有父view if (_customHeaderView && !_customHeaderView.superview) { // 获取_customHeaderView的大小 if (CGSizeEqualToSize(_customViewSize, CGSizeZero)) { // 获取_customHeaderView的大小 _customViewSize = [self sizeForCustomView:_customHeaderView]; } _customHeaderView.translatesAutoresizingMaskIntoConstraints = NO; [self.alertView addSubview:_customHeaderView]; } return _customHeaderView; } - (SPInterfaceActionSequenceView *)actionSequenceView { if (!_actionSequenceView) { SPInterfaceActionSequenceView *actionSequenceView = [[SPInterfaceActionSequenceView alloc] init]; actionSequenceView.translatesAutoresizingMaskIntoConstraints = NO; __weak typeof(self) weakSelf = self; actionSequenceView.buttonClickedInActionViewBlock = ^(NSInteger index) { [weakSelf dismissViewControllerAnimated:YES completion:nil]; SPAlertAction *action = weakSelf.actions[index]; if (action.handler) { action.handler(action); } }; if (self.actions.count && !self.customActionSequenceView) { [self.alertView addSubview:actionSequenceView]; } _actionSequenceView = actionSequenceView; } return _actionSequenceView; } - (UIView *)customActionSequenceView { // _customActionSequenceView有值但是没有父view if (_customActionSequenceView && !_customActionSequenceView.superview) { // 获取_customHeaderView的大小 if (CGSizeEqualToSize(_customViewSize, CGSizeZero)) { // 获取_customActionSequenceView的大小 _customViewSize = [self sizeForCustomView:_customActionSequenceView]; } _customActionSequenceView.translatesAutoresizingMaskIntoConstraints = NO; [self.alertView addSubview:_customActionSequenceView]; } return _customActionSequenceView; } - (SPInterfaceActionItemSeparatorView *)headerActionLine { if (!_headerActionLine) { SPInterfaceActionItemSeparatorView *headerActionLine = [[SPInterfaceActionItemSeparatorView alloc] init]; headerActionLine.translatesAutoresizingMaskIntoConstraints = NO; if ((self.headerView.superview || self.customHeaderView.superview) && (self.actionSequenceView.superview || self.customActionSequenceView.superview)) { [self.alertView addSubview:headerActionLine]; } _headerActionLine = headerActionLine; } return _headerActionLine; } - (UIView *)componentView { if (_componentView && !_componentView.superview) { NSAssert(self.headerActionLine.superview, @"Due to the -componentView is added between the -head and the -action section, the -head and -action must exist together"); // 获取_componentView的大小 if (CGSizeEqualToSize(_customViewSize, CGSizeZero)) { // 获取_componentView的大小 _customViewSize = [self sizeForCustomView:_componentView]; } _componentView.translatesAutoresizingMaskIntoConstraints = NO; [self.alertView addSubview:_componentView]; } return _componentView; } - (SPInterfaceActionItemSeparatorView *)componentActionLine { if (!_componentActionLine) { SPInterfaceActionItemSeparatorView *componentActionLine = [[SPInterfaceActionItemSeparatorView alloc] init]; componentActionLine.translatesAutoresizingMaskIntoConstraints = NO; // 必须组件view和action部分同时存在 if (self.componentView.superview && (self.actionSequenceView.superview || self.customActionSequenceView.superview)) { [self.alertView addSubview:componentActionLine]; } _componentActionLine = componentActionLine; } return _componentActionLine; } - (NSArray *)actions { if (!_actions) { _actions = [NSArray array]; } return _actions; } - (NSArray *)textFields { if (!_textFields) { _textFields = [NSArray array]; } return _textFields; } - (NSMutableArray *)otherActions { if (!_otherActions) { _otherActions = [[NSMutableArray alloc] init]; } return _otherActions; } #pragma mark - UIViewControllerTransitioningDelegate - (nullable id )animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source { return [SPAlertAnimation animationIsPresenting:YES]; } - (nullable id )animationControllerForDismissedController:(UIViewController *)dismissed { [self.view endEditing:YES]; return [SPAlertAnimation animationIsPresenting:NO]; } - (nullable UIPresentationController *)presentationControllerForPresentedViewController:(UIViewController *)presented presentingViewController:(nullable UIViewController *)presenting sourceViewController:(UIViewController *)source NS_AVAILABLE_IOS(8_0) { return [[SPAlertPresentationController alloc] initWithPresentedViewController:presented presentingViewController:presenting]; } #pragma mark - 被废弃的方法 + (instancetype)alertControllerWithTitle:(nullable NSString *)title message:(nullable NSString *)message preferredStyle:(SPAlertControllerStyle)preferredStyle animationType:(SPAlertAnimationType)animationType customView:(UIView *)customView { SPAlertController *alertVc = [[SPAlertController alloc] initWithTitle:nil message:nil customAlertView:customView customHeaderView:nil customActionSequenceView:nil componentView:nil preferredStyle:preferredStyle animationType:animationType]; return alertVc; } + (instancetype)alertControllerWithPreferredStyle:(SPAlertControllerStyle)preferredStyle animationType:(SPAlertAnimationType)animationType customHeaderView:(nullable UIView *)customHeaderView { SPAlertController *alertVc = [[SPAlertController alloc] initWithTitle:nil message:nil customAlertView:nil customHeaderView:customHeaderView customActionSequenceView:nil componentView:nil preferredStyle:preferredStyle animationType:animationType]; return alertVc; } + (instancetype)alertControllerWithTitle:(NSString *)title message:(NSString *)message preferredStyle:(SPAlertControllerStyle)preferredStyle animationType:(SPAlertAnimationType)animationType customCenterView:(UIView *)customCenterView { SPAlertController *alertVc = [[SPAlertController alloc] initWithTitle:title message:message customAlertView:nil customHeaderView:nil customActionSequenceView:nil componentView:customCenterView preferredStyle:preferredStyle animationType:animationType]; return alertVc; } + (instancetype)alertControllerWithTitle:(NSString *)title message:(NSString *)message preferredStyle:(SPAlertControllerStyle)preferredStyle animationType:(SPAlertAnimationType)animationType customFooterView:(UIView *)customFooterView { SPAlertController *alertVc = [[SPAlertController alloc] initWithTitle:title message:message customAlertView:nil customHeaderView:nil customActionSequenceView:customFooterView componentView:nil preferredStyle:preferredStyle animationType:animationType]; return alertVc; } @end #pragma mark ---------------------------- SPAlertController end -------------------------------- @interface SPOverlayView: UIView @property (nonatomic, strong) UIView *presentedView; @property (nonatomic, strong) UIVisualEffectView *effectView; @end @implementation SPOverlayView - (instancetype)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { } return self; } - (void)setAppearanceStyle:(UIBlurEffectStyle)appearanceStyle alpha:(CGFloat)alpha { switch (appearanceStyle) { case -1: { [self.effectView removeFromSuperview]; self.effectView = nil; if (alpha < 0) { alpha = 0.5; } self.backgroundColor = [UIColor colorWithWhite:0 alpha:alpha]; self.alpha = 0; } break; default:{ UIBlurEffect *blur = [UIBlurEffect effectWithStyle:appearanceStyle]; [self createVisualEffectViewWithBlur:blur alpha:alpha]; } } } - (void)createVisualEffectViewWithBlur:(UIBlurEffect *)blur alpha:(CGFloat)alpha { self.backgroundColor = [UIColor clearColor]; UIVisualEffectView *effectView = [[UIVisualEffectView alloc] initWithEffect:blur]; effectView.frame = self.bounds; effectView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; effectView.userInteractionEnabled = NO; effectView.alpha = alpha; [self addSubview:effectView]; _effectView = effectView; } @end #pragma mark ---------------------------- SPAlertPresentationController begin -------------------------------- @interface SPAlertPresentationController() @property (nonatomic, strong) SPOverlayView *overlayView; @end @implementation SPAlertPresentationController - (instancetype)initWithPresentedViewController:(UIViewController *)presentedViewController presentingViewController:(UIViewController *)presentingViewController { if (self = [super initWithPresentedViewController:presentedViewController presentingViewController:presentingViewController]) { } return self; } - (void)containerViewWillLayoutSubviews { [super containerViewWillLayoutSubviews]; self.overlayView.frame = self.containerView.bounds; } - (void)containerViewDidLayoutSubviews { [super containerViewDidLayoutSubviews]; } - (void)presentationTransitionWillBegin { [super presentationTransitionWillBegin]; SPAlertController *alertController = (SPAlertController *)self.presentedViewController; [self.overlayView setAppearanceStyle:alertController.backgroundViewAppearanceStyle alpha:alertController.backgroundViewAlpha]; // 遮罩的alpha值从0~1变化,UIViewControllerTransitionCoordinator协是一个过渡协调器,当执行模态过渡或push过渡时,可以对视图中的其他部分做动画 id coordinator = [self.presentedViewController transitionCoordinator]; if (coordinator) { [coordinator animateAlongsideTransition:^(id context) { self.overlayView.alpha = 1.0; } completion:nil]; } else { self.overlayView.alpha = 1.0; } if ([alertController.delegate respondsToSelector:@selector(willPresentAlertController:)]) { [alertController.delegate willPresentAlertController:alertController]; } else if ([alertController.delegate respondsToSelector:@selector(sp_alertControllerWillShow:)]) { // 支持老版本 [alertController.delegate sp_alertControllerWillShow:alertController]; } } - (void)presentationTransitionDidEnd:(BOOL)completed { [super presentationTransitionDidEnd:completed]; SPAlertController *alertController = (SPAlertController *)self.presentedViewController; if ([alertController.delegate respondsToSelector:@selector(didPresentAlertController:)]) { [alertController.delegate didPresentAlertController:alertController]; } else if ([alertController.delegate respondsToSelector:@selector(sp_alertControllerDidShow:)]) { // 支持老版本 [alertController.delegate sp_alertControllerDidShow:alertController]; } } - (void)dismissalTransitionWillBegin { [super dismissalTransitionWillBegin]; // 遮罩的alpha值从1~0变化,UIViewControllerTransitionCoordinator协议执行动画可以保证和转场动画同步 id coordinator = [self.presentedViewController transitionCoordinator]; if (coordinator) { [coordinator animateAlongsideTransition:^(id context) { self.overlayView.alpha = 0.0; } completion:nil]; } else { self.overlayView.alpha = 0.0; } SPAlertController *alertController = (SPAlertController *)self.presentedViewController; if ([alertController.delegate respondsToSelector:@selector(willDismissAlertController:)]) { [alertController.delegate willDismissAlertController:alertController]; } else if ([alertController.delegate respondsToSelector:@selector(sp_alertControllerWillHide:)]) { // 支持老版本 [alertController.delegate sp_alertControllerWillHide:alertController]; } } - (void)dismissalTransitionDidEnd:(BOOL)completed { [super dismissalTransitionDidEnd:completed]; if (completed) { [_overlayView removeFromSuperview]; _overlayView = nil; } SPAlertController *alertController = (SPAlertController *)self.presentedViewController; if ([alertController.delegate respondsToSelector:@selector(didDismissAlertController:)]) { [alertController.delegate didDismissAlertController:alertController]; } else if ([alertController.delegate respondsToSelector:@selector(sp_alertControllerDidHide:)]) { // 支持老版本 [alertController.delegate sp_alertControllerDidHide:alertController]; } } - (CGRect)frameOfPresentedViewInContainerView{ return self.presentedView.frame; } - (void)tapOverlayView { SPAlertController *alertController = (SPAlertController *)self.presentedViewController; if (alertController.tapBackgroundViewDismiss) { [alertController dismissViewControllerAnimated:YES completion:^{}]; } } - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; } - (SPOverlayView *)overlayView { if (!_overlayView) { _overlayView = [[SPOverlayView alloc] init]; _overlayView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapOverlayView)]; [_overlayView addGestureRecognizer:tap]; [self.containerView addSubview:_overlayView]; } return _overlayView; } @end #pragma mark ---------------------------- SPAlertPresentationController end -------------------------------- #pragma mark ---------------------------- SPAlertAnimation begin -------------------------------- @interface SPAlertAnimation() @property (nonatomic, assign) BOOL presenting; @end @implementation SPAlertAnimation + (instancetype)animationIsPresenting:(BOOL)isPresenting { return [[self alloc] initWithPresenting:isPresenting]; } - (instancetype)initWithPresenting:(BOOL)isPresenting { if (self = [super init]) { self.presenting = isPresenting; } return self; } #pragma mark - UIViewControllerAnimatedTransitioning - (NSTimeInterval)transitionDuration:(nullable id )transitionContext { return 0.25f; } - (void)animateTransition:(id )transitionContext { if (self.presenting) { [self presentAnimationTransition:transitionContext]; } else { [self dismissAnimationTransition:transitionContext]; } } - (void)presentAnimationTransition:(id)transitionContext { SPAlertController *alertController = (SPAlertController *)[transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]; switch (alertController.animationType) { case SPAlertAnimationTypeRaiseUp: case SPAlertAnimationTypeFromBottom: [self raiseUpWhenPresentForController:alertController transition:transitionContext]; break; case SPAlertAnimationTypeFromRight: [self fromRightWhenPresentForController:alertController transition:transitionContext]; break; case SPAlertAnimationTypeDropDown: case SPAlertAnimationTypeFromTop: [self dropDownWhenPresentForController:alertController transition:transitionContext]; break; case SPAlertAnimationTypeFromLeft: [self fromLeftWhenPresentForController:alertController transition:transitionContext]; break; case SPAlertAnimationTypeAlpha: case SPAlertAnimationTypeFade: [self alphaWhenPresentForController:alertController transition:transitionContext]; break; case SPAlertAnimationTypeExpand: [self expandWhenPresentForController:alertController transition:transitionContext]; break; case SPAlertAnimationTypeShrink: [self shrinkWhenPresentForController:alertController transition:transitionContext]; break; case SPAlertAnimationTypeNone: [self noneWhenPresentForController:alertController transition:transitionContext]; break; default: break; } } - (void)dismissAnimationTransition:(id)transitionContext { SPAlertController *alertController = (SPAlertController *)[transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey]; if ([alertController isKindOfClass:[SPAlertController class]]) { switch (alertController.animationType) { case SPAlertAnimationTypeRaiseUp: case SPAlertAnimationTypeFromBottom: [self dismissCorrespondingRaiseUpForController:alertController transition:transitionContext]; break; case SPAlertAnimationTypeFromRight: [self dismissCorrespondingFromRightForController:alertController transition:transitionContext]; break; case SPAlertAnimationTypeFromLeft: [self dismissCorrespondingFromLeftForController:alertController transition:transitionContext]; break; case SPAlertAnimationTypeDropDown: case SPAlertAnimationTypeFromTop: [self dismissCorrespondingDropDownForController:alertController transition:transitionContext]; break; case SPAlertAnimationTypeAlpha: case SPAlertAnimationTypeFade: [self dismissCorrespondingAlphaForController:alertController transition:transitionContext]; break; case SPAlertAnimationTypeExpand: [self dismissCorrespondingExpandForController:alertController transition:transitionContext]; break; case SPAlertAnimationTypeShrink: [self dismissCorrespondingShrinkForController:alertController transition:transitionContext]; break; case SPAlertAnimationTypeNone: [self dismissCorrespondingNoneForController:alertController transition:transitionContext]; break; default: break; } } } // 从底部弹出的present动画 - (void)raiseUpWhenPresentForController:(SPAlertController *)alertController transition:(id)transitionContext { UIView *containerView = [transitionContext containerView]; // 将alertController的view添加到containerView上 [containerView addSubview:alertController.view]; // 标记需要刷新布局 [containerView setNeedsLayout]; // 在有标记刷新布局的情况下立即布局,这行代码很重要,第一:立即布局会立即调用SPAlertController的viewWillLayoutSubviews的方法,第二:立即布局后可以获取到alertController.view的frame [containerView layoutIfNeeded]; // 这3行代码不能放在[containerView layoutIfNeeded]之前,如果放在之前,[containerView layoutIfNeeded]强制布局后会将以下设置的frame覆盖 CGRect controlViewFrame = alertController.view.frame; controlViewFrame.origin.y = SP_SCREEN_HEIGHT; alertController.view.frame = controlViewFrame; [UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0.0 options:UIViewAnimationOptionCurveEaseOut animations:^{ CGRect controlViewFrame = alertController.view.frame; if (alertController.preferredStyle == SPAlertControllerStyleActionSheet) { controlViewFrame.origin.y = SP_SCREEN_HEIGHT-controlViewFrame.size.height; } else { controlViewFrame.origin.y = (SP_SCREEN_HEIGHT-controlViewFrame.size.height) / 2.0; [self offSetCenter:alertController]; } alertController.view.frame = controlViewFrame; } completion:^(BOOL finished) { [transitionContext completeTransition:finished]; [alertController layoutAlertControllerView]; }]; } // 从底部弹出对应的dismiss动画 - (void)dismissCorrespondingRaiseUpForController:(SPAlertController *)alertController transition:(id)transitionContext { [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{ CGRect controlViewFrame = alertController.view.frame; controlViewFrame.origin.y = SP_SCREEN_HEIGHT; alertController.view.frame = controlViewFrame; } completion:^(BOOL finished) { [transitionContext completeTransition:finished]; }]; } // 从右边弹出的present动画 - (void)fromRightWhenPresentForController:(SPAlertController *)alertController transition:(id)transitionContext { UIView *containerView = [transitionContext containerView]; // 将alertController的view添加到containerView上 [containerView addSubview:alertController.view]; // 标记需要刷新布局 [containerView setNeedsLayout]; // 在有标记刷新布局的情况下立即布局,这行代码很重要,第一:立即布局会立即调用SPAlertController的viewWillLayoutSubviews的方法,第二:立即布局后可以获取到alertController.view的frame [containerView layoutIfNeeded]; // 这3行代码不能放在[containerView layoutIfNeeded]之前,如果放在之前,[containerView layoutIfNeeded]强制布局后会将以下设置的frame覆盖 CGRect controlViewFrame = alertController.view.frame; controlViewFrame.origin.x = SP_SCREEN_WIDTH; alertController.view.frame = controlViewFrame; if (alertController.preferredStyle == SPAlertControllerStyleAlert) { [self offSetCenter:alertController]; } [UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0.0 options:UIViewAnimationOptionCurveEaseOut animations:^{ CGRect controlViewFrame = alertController.view.frame; if (alertController.preferredStyle == SPAlertControllerStyleActionSheet) { controlViewFrame.origin.x = SP_SCREEN_WIDTH-controlViewFrame.size.width; } else { controlViewFrame.origin.x = (SP_SCREEN_WIDTH-controlViewFrame.size.width) / 2.0; } alertController.view.frame = controlViewFrame; } completion:^(BOOL finished) { [transitionContext completeTransition:finished]; [alertController layoutAlertControllerView]; }]; } // 从右边弹出对应的dismiss动画 - (void)dismissCorrespondingFromRightForController:(SPAlertController *)alertController transition:(id)transitionContext { [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{ CGRect controlViewFrame = alertController.view.frame; controlViewFrame.origin.x = SP_SCREEN_WIDTH; alertController.view.frame = controlViewFrame; } completion:^(BOOL finished) { [transitionContext completeTransition:finished]; }]; } // 从左边弹出的present动画 - (void)fromLeftWhenPresentForController:(SPAlertController *)alertController transition:(id)transitionContext { UIView *containerView = [transitionContext containerView]; // 将alertController的view添加到containerView上 [containerView addSubview:alertController.view]; // 标记需要刷新布局 [containerView setNeedsLayout]; // 在有标记刷新布局的情况下立即布局,这行代码很重要,第一:立即布局会立即调用SPAlertController的viewWillLayoutSubviews的方法,第二:立即布局后可以获取到alertController.view的frame [containerView layoutIfNeeded]; // 这3行代码不能放在[containerView layoutIfNeeded]之前,如果放在之前,[containerView layoutIfNeeded]强制布局后会将以下设置的frame覆盖 CGRect controlViewFrame = alertController.view.frame; controlViewFrame.origin.x = -controlViewFrame.size.width; alertController.view.frame = controlViewFrame; if (alertController.preferredStyle == SPAlertControllerStyleAlert) { [self offSetCenter:alertController]; } [UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0.0 options:UIViewAnimationOptionCurveEaseOut animations:^{ CGRect controlViewFrame = alertController.view.frame; if (alertController.preferredStyle == SPAlertControllerStyleActionSheet) { controlViewFrame.origin.x = 0; } else { controlViewFrame.origin.x = (SP_SCREEN_WIDTH-controlViewFrame.size.width) / 2.0; } alertController.view.frame = controlViewFrame; } completion:^(BOOL finished) { [transitionContext completeTransition:finished]; [alertController layoutAlertControllerView]; }]; } // 从左边弹出对应的dismiss动画 - (void)dismissCorrespondingFromLeftForController:(SPAlertController *)alertController transition:(id)transitionContext { [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{ CGRect controlViewFrame = alertController.view.frame; controlViewFrame.origin.x = -controlViewFrame.size.width; alertController.view.frame = controlViewFrame; } completion:^(BOOL finished) { [transitionContext completeTransition:finished]; }]; } // 从顶部弹出的present动画 - (void)dropDownWhenPresentForController:(SPAlertController *)alertController transition:(id)transitionContext { UIView *containerView = [transitionContext containerView]; // 将alertController的view添加到containerView上 [containerView addSubview:alertController.view]; // 标记需要刷新布局 [containerView setNeedsLayout]; // 在有标记刷新布局的情况下立即布局,这行代码很重要,第一:立即布局会立即调用SPAlertController的viewWillLayoutSubviews的方法,第二:立即布局后可以获取到alertController.view的frame [containerView layoutIfNeeded]; // 这3行代码不能放在[containerView layoutIfNeeded]之前,如果放在之前,[containerView layoutIfNeeded]强制布局后会将以下设置的frame覆盖 CGRect controlViewFrame = alertController.view.frame; controlViewFrame.origin.y = -controlViewFrame.size.height; alertController.view.frame = controlViewFrame; [UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0.0 options:UIViewAnimationOptionCurveEaseOut animations:^{ CGRect controlViewFrame = alertController.view.frame; if (alertController.preferredStyle == SPAlertControllerStyleActionSheet) { controlViewFrame.origin.y = 0; } else { controlViewFrame.origin.y = (SP_SCREEN_HEIGHT-controlViewFrame.size.height) / 2.0; [self offSetCenter:alertController]; } alertController.view.frame = controlViewFrame; } completion:^(BOOL finished) { [transitionContext completeTransition:finished]; [alertController layoutAlertControllerView]; }]; } // 从顶部弹出对应的dismiss动画 - (void)dismissCorrespondingDropDownForController:(SPAlertController *)alertController transition:(id)transitionContext { [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{ CGRect controlViewFrame = alertController.view.frame; controlViewFrame.origin.y = -controlViewFrame.size.height; alertController.view.frame = controlViewFrame; } completion:^(BOOL finished) { [transitionContext completeTransition:finished]; }]; } // alpha值从0到1变化的present动画 - (void)alphaWhenPresentForController:(SPAlertController *)alertController transition:(id)transitionContext { UIView *containerView = [transitionContext containerView]; [containerView addSubview:alertController.view]; // 标记需要刷新布局 [containerView setNeedsLayout]; // 在有标记刷新布局的情况下立即布局,这行代码很重要,第一:立即布局会立即调用SPAlertController的viewWillLayoutSubviews的方法,第二:立即布局后可以获取到alertController.view的frame,不仅如此,走了viewWillLayoutSubviews键盘就会弹出,此后可以获取到alertController.offset [containerView layoutIfNeeded]; alertController.view.alpha = 0; [UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0.0 options:UIViewAnimationOptionCurveLinear animations:^{ [self offSetCenter:alertController]; alertController.view.alpha = 1; } completion:^(BOOL finished) { [transitionContext completeTransition:finished]; [alertController layoutAlertControllerView]; }]; } // alpha值从0到1变化对应的的dismiss动画 - (void)dismissCorrespondingAlphaForController:(SPAlertController *)alertController transition:(id)transitionContext { [UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0.0 options:UIViewAnimationOptionCurveLinear animations:^{ alertController.view.alpha = 0; } completion:^(BOOL finished) { [transitionContext completeTransition:finished]; }]; } // 发散的prensent动画 - (void)expandWhenPresentForController:(SPAlertController *)alertController transition:(id)transitionContext { UIView *containerView = [transitionContext containerView]; [containerView addSubview:alertController.view]; // 标记需要刷新布局 [containerView setNeedsLayout]; // 在有标记刷新布局的情况下立即布局,这行代码很重要,第一:立即布局会立即调用SPAlertController的viewWillLayoutSubviews的方法,第二:立即布局后可以获取到alertController.view的frame,不仅如此,走了viewWillLayoutSubviews键盘就会弹出,此后可以获取到alertController.offset [containerView layoutIfNeeded]; alertController.view.transform = CGAffineTransformMakeScale(0.9, 0.9); alertController.view.alpha = 0.0; [UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0.0 options:UIViewAnimationOptionCurveLinear animations:^{ [self offSetCenter:alertController]; alertController.view.transform = CGAffineTransformIdentity; alertController.view.alpha = 1.0; } completion:^(BOOL finished) { [transitionContext completeTransition:finished]; [alertController layoutAlertControllerView]; }]; } // 发散对应的dismiss动画 - (void)dismissCorrespondingExpandForController:(SPAlertController *)alertController transition:(id)transitionContext { [UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0.0 options:UIViewAnimationOptionCurveLinear animations:^{ alertController.view.transform = CGAffineTransformIdentity; alertController.view.alpha = 0.0; } completion:^(BOOL finished) { [transitionContext completeTransition:finished]; }]; } // 收缩的present动画 - (void)shrinkWhenPresentForController:(SPAlertController *)alertController transition:(id)transitionContext { UIView *containerView = [transitionContext containerView]; [containerView addSubview:alertController.view]; // 标记需要刷新布局 [containerView setNeedsLayout]; // 在有标记刷新布局的情况下立即布局,这行代码很重要,第一:立即布局会立即调用SPAlertController的viewWillLayoutSubviews的方法,第二:立即布局后可以获取到alertController.view的frame,不仅如此,走了viewWillLayoutSubviews键盘就会弹出,此后可以获取到alertController.offset [containerView layoutIfNeeded]; alertController.view.transform = CGAffineTransformMakeScale(1.1, 1.1); alertController.view.alpha = 0; [UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0.0 options:UIViewAnimationOptionCurveLinear animations:^{ [self offSetCenter:alertController]; alertController.view.transform = CGAffineTransformIdentity; alertController.view.alpha = 1.0; } completion:^(BOOL finished) { [transitionContext completeTransition:finished]; [alertController layoutAlertControllerView]; }]; } // 收缩对应的的dismiss动画 - (void)dismissCorrespondingShrinkForController:(SPAlertController *)alertController transition:(id)transitionContext { // 与发散对应的dismiss动画相同 [self dismissCorrespondingExpandForController:alertController transition:transitionContext]; } // 无动画 - (void)noneWhenPresentForController:(SPAlertController *)alertController transition:(id)transitionContext { UIView *containerView = [transitionContext containerView]; [containerView addSubview:alertController.view]; [transitionContext completeTransition:transitionContext.animated]; } - (void)dismissCorrespondingNoneForController:(SPAlertController *)alertController transition:(id)transitionContext { [transitionContext completeTransition:transitionContext.animated]; } - (void)offSetCenter:(SPAlertController *)alertController { if (!CGPointEqualToPoint(alertController.offsetForAlert, CGPointZero)) { CGPoint controlViewCenter = alertController.view.center; controlViewCenter.x = SP_SCREEN_WIDTH / 2.0 + alertController.offsetForAlert.x; controlViewCenter.y = SP_SCREEN_HEIGHT / 2.0 + alertController.offsetForAlert.y; alertController.view.center = controlViewCenter; } } @end #pragma clang diagnostic pop #pragma mark ---------------------------- SPAlertAnimation end --------------------------------