diff --git a/Ifish.xcodeproj/project.pbxproj b/Ifish.xcodeproj/project.pbxproj index 84e3e1c..9a87e9a 100644 --- a/Ifish.xcodeproj/project.pbxproj +++ b/Ifish.xcodeproj/project.pbxproj @@ -2304,6 +2304,8 @@ C0553C2628856DBD00AB7B50 /* topround.png in Resources */ = {isa = PBXBuildFile; fileRef = C0553C2428856DBC00AB7B50 /* topround.png */; }; C0553C2728856DBD00AB7B50 /* bottomRound.png in Resources */ = {isa = PBXBuildFile; fileRef = C0553C2528856DBD00AB7B50 /* bottomRound.png */; }; C0553C292885962B00AB7B50 /* centerrect.png in Resources */ = {isa = PBXBuildFile; fileRef = C0553C282885962A00AB7B50 /* centerrect.png */; }; + C0569BEA2A91162C00EE1734 /* SPAlertController.m in Sources */ = {isa = PBXBuildFile; fileRef = C0569BE82A91162C00EE1734 /* SPAlertController.m */; }; + C0569BEB2A91162D00EE1734 /* SPAlertController.h in Headers */ = {isa = PBXBuildFile; fileRef = C0569BE92A91162C00EE1734 /* SPAlertController.h */; }; C057166C282376CC004F113A /* UIButton+ImageTitleStyle.h in Headers */ = {isa = PBXBuildFile; fileRef = C057166A282376CC004F113A /* UIButton+ImageTitleStyle.h */; }; C057166D282376CC004F113A /* UIButton+ImageTitleStyle.m in Sources */ = {isa = PBXBuildFile; fileRef = C057166B282376CC004F113A /* UIButton+ImageTitleStyle.m */; }; C05716702823D653004F113A /* XuanduoTimerListViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = C057166E2823D652004F113A /* XuanduoTimerListViewController.h */; }; @@ -2339,9 +2341,9 @@ C0B2F55D244D5577001079AA /* StoreNameView.h in Headers */ = {isa = PBXBuildFile; fileRef = C0B2F55B244D5577001079AA /* StoreNameView.h */; }; C0B2F55E244D5577001079AA /* StoreNameView.m in Sources */ = {isa = PBXBuildFile; fileRef = C0B2F55C244D5577001079AA /* StoreNameView.m */; }; C0B2F560244D568E001079AA /* StoreNameView.xib in Resources */ = {isa = PBXBuildFile; fileRef = C0B2F55F244D568E001079AA /* StoreNameView.xib */; }; - C0B37E0C2A8BA81B00CC9EB7 /* ConfigWfiViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = C0B37E092A8BA81B00CC9EB7 /* ConfigWfiViewController.h */; }; - C0B37E0D2A8BA81B00CC9EB7 /* ConfigWfiViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C0B37E0A2A8BA81B00CC9EB7 /* ConfigWfiViewController.m */; }; - C0B37E0E2A8BA81B00CC9EB7 /* ConfigWfiViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = C0B37E0B2A8BA81B00CC9EB7 /* ConfigWfiViewController.xib */; }; + C0B37E0C2A8BA81B00CC9EB7 /* ConfigWifiViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = C0B37E092A8BA81B00CC9EB7 /* ConfigWifiViewController.h */; }; + C0B37E0D2A8BA81B00CC9EB7 /* ConfigWifiViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C0B37E0A2A8BA81B00CC9EB7 /* ConfigWifiViewController.m */; }; + C0B37E0E2A8BA81B00CC9EB7 /* ConfigWifiViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = C0B37E0B2A8BA81B00CC9EB7 /* ConfigWifiViewController.xib */; }; C0B37E112A8BAB2300CC9EB7 /* EspTouchDelegateImpl.h in Headers */ = {isa = PBXBuildFile; fileRef = C0B37E0F2A8BAB2300CC9EB7 /* EspTouchDelegateImpl.h */; }; C0B37E122A8BAB2300CC9EB7 /* EspTouchDelegateImpl.m in Sources */ = {isa = PBXBuildFile; fileRef = C0B37E102A8BAB2300CC9EB7 /* EspTouchDelegateImpl.m */; }; C0C4CCFF272CDF4A00574BE8 /* UIViewController+Navgation.m in Sources */ = {isa = PBXBuildFile; fileRef = C0C4CCFD272CDF4A00574BE8 /* UIViewController+Navgation.m */; }; @@ -5834,6 +5836,8 @@ C0553C2428856DBC00AB7B50 /* topround.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = topround.png; sourceTree = ""; }; C0553C2528856DBD00AB7B50 /* bottomRound.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = bottomRound.png; sourceTree = ""; }; C0553C282885962A00AB7B50 /* centerrect.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = centerrect.png; sourceTree = ""; }; + C0569BE82A91162C00EE1734 /* SPAlertController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPAlertController.m; sourceTree = ""; }; + C0569BE92A91162C00EE1734 /* SPAlertController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPAlertController.h; sourceTree = ""; }; C057166A282376CC004F113A /* UIButton+ImageTitleStyle.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIButton+ImageTitleStyle.h"; sourceTree = ""; }; C057166B282376CC004F113A /* UIButton+ImageTitleStyle.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIButton+ImageTitleStyle.m"; sourceTree = ""; }; C057166E2823D652004F113A /* XuanduoTimerListViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XuanduoTimerListViewController.h; sourceTree = ""; }; @@ -5869,9 +5873,9 @@ C0B2F55B244D5577001079AA /* StoreNameView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = StoreNameView.h; sourceTree = ""; }; C0B2F55C244D5577001079AA /* StoreNameView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = StoreNameView.m; sourceTree = ""; }; C0B2F55F244D568E001079AA /* StoreNameView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = StoreNameView.xib; sourceTree = ""; }; - C0B37E092A8BA81B00CC9EB7 /* ConfigWfiViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ConfigWfiViewController.h; sourceTree = ""; }; - C0B37E0A2A8BA81B00CC9EB7 /* ConfigWfiViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ConfigWfiViewController.m; sourceTree = ""; }; - C0B37E0B2A8BA81B00CC9EB7 /* ConfigWfiViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ConfigWfiViewController.xib; sourceTree = ""; }; + C0B37E092A8BA81B00CC9EB7 /* ConfigWifiViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ConfigWifiViewController.h; sourceTree = ""; }; + C0B37E0A2A8BA81B00CC9EB7 /* ConfigWifiViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ConfigWifiViewController.m; sourceTree = ""; }; + C0B37E0B2A8BA81B00CC9EB7 /* ConfigWifiViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ConfigWifiViewController.xib; sourceTree = ""; }; C0B37E0F2A8BAB2300CC9EB7 /* EspTouchDelegateImpl.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = EspTouchDelegateImpl.h; sourceTree = ""; }; C0B37E102A8BAB2300CC9EB7 /* EspTouchDelegateImpl.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = EspTouchDelegateImpl.m; sourceTree = ""; }; C0C4CCFD272CDF4A00574BE8 /* UIViewController+Navgation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIViewController+Navgation.m"; sourceTree = ""; }; @@ -10120,9 +10124,9 @@ 883E78181D48A5E50030E075 /* SaySomethingViewController.m */, 883E78191D48A5E50030E075 /* SaySomethingViewController.xib */, 883E781A1D48A5E50030E075 /* SecondConnectWifiController.h */, - C0B37E092A8BA81B00CC9EB7 /* ConfigWfiViewController.h */, - C0B37E0A2A8BA81B00CC9EB7 /* ConfigWfiViewController.m */, - C0B37E0B2A8BA81B00CC9EB7 /* ConfigWfiViewController.xib */, + C0B37E092A8BA81B00CC9EB7 /* ConfigWifiViewController.h */, + C0B37E0A2A8BA81B00CC9EB7 /* ConfigWifiViewController.m */, + C0B37E0B2A8BA81B00CC9EB7 /* ConfigWifiViewController.xib */, 883E781B1D48A5E50030E075 /* SecondConnectWifiController.m */, 883E781C1D48A5E50030E075 /* SecondConnectWifiController.xib */, E5317A1421188F8A0014AFDD /* RestartDeviceModel.h */, @@ -12209,6 +12213,7 @@ 88F5ECA41D48C9C400CC7CAF /* ThirdPartUnity */ = { isa = PBXGroup; children = ( + C0569BE72A91162C00EE1734 /* SPAlertController */, 2AC874DC26874F7500B6161C /* ESPTouch */, C0D3A11B2647EB9F008E6FD6 /* ZLRocker */, 18C54F032350541400DF4E7D /* AlibcTradeSDK-3.1.1.96 */, @@ -12467,6 +12472,15 @@ path = HTTPDNS; sourceTree = ""; }; + C0569BE72A91162C00EE1734 /* SPAlertController */ = { + isa = PBXGroup; + children = ( + C0569BE82A91162C00EE1734 /* SPAlertController.m */, + C0569BE92A91162C00EE1734 /* SPAlertController.h */, + ); + path = SPAlertController; + sourceTree = ""; + }; C0714DFC2A6FC3B300182CA8 /* airkiss */ = { isa = PBXGroup; children = ( @@ -13200,6 +13214,7 @@ CB484DE322B8C8C80075F050 /* pixdesc.h in Headers */, CB48204D2334D0A200A50C92 /* ConAquarChooseWiFiVC.h in Headers */, 3D1C50B1221A9EDF0096AE43 /* YMsgBox.h in Headers */, + C0569BEB2A91162D00EE1734 /* SPAlertController.h in Headers */, CBA6167D228F9AB100ED380D /* MASViewAttribute.h in Headers */, 3D1C50BD221A9EE00096AE43 /* LoginResult.h in Headers */, CB7D6AA522953BEB0014E5C7 /* YYCache.h in Headers */, @@ -13258,7 +13273,7 @@ CBA615FC228E8E5A00ED380D /* MyMessageViewController.h in Headers */, CB484E1522B8C8C90075F050 /* avfft.h in Headers */, CB484E0022B8C8C80075F050 /* channel_layout.h in Headers */, - C0B37E0C2A8BA81B00CC9EB7 /* ConfigWfiViewController.h in Headers */, + C0B37E0C2A8BA81B00CC9EB7 /* ConfigWifiViewController.h in Headers */, CBB0243123517947002900D5 /* MJFoundation.h in Headers */, 3D1C512B221A9EE10096AE43 /* IfishCameraRecordfirstCell.h in Headers */, 402591952238D91400CE4900 /* UINavigationController+Config.h in Headers */, @@ -14631,7 +14646,7 @@ C0B2F560244D568E001079AA /* StoreNameView.xib in Resources */, 882956211DBDA3A100E9DDD7 /* neves0036.png in Resources */, 8865120C1E979BF100BABDF1 /* personal_iocn_share@3x.png in Resources */, - C0B37E0E2A8BA81B00CC9EB7 /* ConfigWfiViewController.xib in Resources */, + C0B37E0E2A8BA81B00CC9EB7 /* ConfigWifiViewController.xib in Resources */, 8814781B1E712A2700BFB79C /* task_ifishDoctorpng.png in Resources */, 882957591DBDA3A300E9DDD7 /* swimmingfish0072.png in Resources */, 883E78821D48A5E50030E075 /* ChangeMobleController.xib in Resources */, @@ -15289,6 +15304,7 @@ 3D1C511C221A9EE00096AE43 /* IfishCameraModel.m in Sources */, 881672341EA5DE2B00BEBF23 /* UMComLikeButtonTableViewCell.m in Sources */, 88F5EFD31D48D08100CC7CAF /* MMProgressHUD.m in Sources */, + C0569BEA2A91162C00EE1734 /* SPAlertController.m in Sources */, 88779EB21E28BE7D00209ABA /* IfishShopDetailViewController.m in Sources */, 883E78991D48A5E50030E075 /* IfishDeviceViewController.m in Sources */, 8853D99A1E24EED700776BF4 /* ShuoMingShuCell.m in Sources */, @@ -15698,7 +15714,7 @@ 886970051E9DFA12005D4AFB /* IfishGoodsData.m in Sources */, 88140AA91D86B84300FE34E5 /* LxGetCurrentIp.m in Sources */, 88653C9E1E76697000FF973E /* IfishInformations.m in Sources */, - C0B37E0D2A8BA81B00CC9EB7 /* ConfigWfiViewController.m in Sources */, + C0B37E0D2A8BA81B00CC9EB7 /* ConfigWifiViewController.m in Sources */, CB7D6AA622953BEB0014E5C7 /* YYKVStorage.m in Sources */, 886F4DF11D753858001EDA34 /* Report2ViewCell.m in Sources */, 881671EB1EA5DE2B00BEBF23 /* UMComCommentEditView.m in Sources */, diff --git a/Ifish/SPAlertController/SPAlertController.h b/Ifish/SPAlertController/SPAlertController.h new file mode 100755 index 0000000..19eaf7d --- /dev/null +++ b/Ifish/SPAlertController/SPAlertController.h @@ -0,0 +1,223 @@ +// +// SPAlertController.h +// SPAlertController +// +// Created by 乐升平 on 18/10/12. https://github.com/SPStore/SPAlertController +// Copyright © 2018-2019 leshengping (lesp163@163.com). All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +typedef NS_ENUM(NSInteger, SPAlertControllerStyle) { + SPAlertControllerStyleActionSheet = 0, // 从单侧弹出(顶/左/底/右) + SPAlertControllerStyleAlert, // 从中间弹出 +}; + +typedef NS_ENUM(NSInteger, SPAlertAnimationType) { + SPAlertAnimationTypeDefault = 0, // 默认动画,如果是SPAlertControllerStyleActionSheet样式,默认动画等效于SPAlertAnimationTypeFromBottom,如果是SPAlertControllerStyleAlert样式,默认动画等效于SPAlertAnimationTypeShrink + SPAlertAnimationTypeFromBottom, // 从底部弹出 + SPAlertAnimationTypeFromTop, // 从顶部弹出 + SPAlertAnimationTypeFromRight, // 从右边弹出 + SPAlertAnimationTypeFromLeft, // 从左边弹出 + + SPAlertAnimationTypeShrink, // 收缩动画 + SPAlertAnimationTypeExpand, // 发散动画 + SPAlertAnimationTypeFade, // 渐变动画 + + SPAlertAnimationTypeNone, // 无动画 + SPAlertAnimationTypeAlpha NS_ENUM_DEPRECATED_IOS(8_0, 8_0, "Use SPAlertAnimationTypeFade instead"), // 渐变动画 + SPAlertAnimationTypeRaiseUp NS_ENUM_DEPRECATED_IOS(8_0, 8_0, "Use SPAlertAnimationTypeFromBottom instead"), // 从底部弹出 + SPAlertAnimationTypeDropDown NS_ENUM_DEPRECATED_IOS(8_0, 8_0, "Use SPAlertAnimationTypeFromTop instead"), // 从顶部弹出 +}; + +typedef NS_ENUM(NSInteger, SPAlertActionStyle) { + SPAlertActionStyleDefault = 0, // 默认样式 + SPAlertActionStyleCancel, // 取消样式,字体加粗 + SPAlertActionStyleDestructive // 红色字体样式 +}; + +// ===================================================== SPAlertAction ===================================================== + +@interface SPAlertAction : NSObject + ++ (instancetype)actionWithTitle:(nullable NSString *)title style:(SPAlertActionStyle)style handler:(void (^ __nullable)(SPAlertAction *action))handler; + +/** action的标题 */ +@property(nullable, nonatomic, copy) NSString *title; +/** action的富文本标题 */ +@property(nullable, nonatomic, copy) NSAttributedString *attributedTitle; +/** action的图标,位于title的左边 */ +@property(nullable, nonatomic, copy) UIImage *image; +/** title跟image之间的间距 */ +@property (nonatomic, assign) CGFloat imageTitleSpacing; +/** 渲染颜色,当外部的图片使用了UIImageRenderingModeAlwaysTemplate时,使用该属性可改变图片的颜色 */ +@property (nonatomic, strong) UIColor *tintColor; +/** 是否能点击,默认为YES */ +@property(nonatomic, getter=isEnabled) BOOL enabled; +/** action的标题颜色,这个颜色只是普通文本的颜色,富文本颜色需要用NSForegroundColorAttributeName */ +@property(nonatomic, strong) UIColor *titleColor; +/** action的标题字体,如果文字太长显示不全,会自动改变字体自适应按钮宽度,最多压缩文字为原来的0.5倍封顶 */ +@property(nonatomic, strong) UIFont *titleFont; +/** action的标题的内边距,如果在不改变字体的情况下想增大action的高度,可以设置该属性的top和bottom值,默认UIEdgeInsetsMake(0, 15, 0, 15) */ +@property(nonatomic, assign) UIEdgeInsets titleEdgeInsets; + +/** 样式 */ +@property(nonatomic, readonly) SPAlertActionStyle style; + +@end + +// ===================================================== SPAlertController ===================================================== + +@protocol SPAlertControllerDelegate; +@interface SPAlertController : UIViewController + ++ (instancetype)alertControllerWithTitle:(nullable NSString *)title message:(nullable NSString *)message preferredStyle:(SPAlertControllerStyle)preferredStyle; ++ (instancetype)alertControllerWithTitle:(nullable NSString *)title message:(nullable NSString *)message preferredStyle:(SPAlertControllerStyle)preferredStyle animationType:(SPAlertAnimationType)animationType; + +- (void)addAction:(SPAlertAction *)action; +@property (nonatomic, readonly) NSArray *actions; + +/* 添加文本输入框 + * 一旦添加后就会回调一次(仅回调一次,因此可以在这个block块里面自由定制textFiled,如设置textField的属性,设置代理,添加addTarget,监听通知等); + */ +- (void)addTextFieldWithConfigurationHandler:(void (^ __nullable)(UITextField *textField))configurationHandler; +@property(nullable, nonatomic, readonly) NSArray *textFields; + +/** 主标题 */ +@property(nullable, nonatomic, copy) NSString *title; +/** 副标题 */ +@property(nullable, nonatomic, copy) NSString *message; +/** 主标题(富文本) */ +@property(nullable, nonatomic, copy) NSAttributedString *attributedTitle; +/** 副标题(富文本) */ +@property(nullable, nonatomic, copy) NSAttributedString *attributedMessage; +/** 头部图标,位置处于title之上,大小取决于图片本身大小 */ +@property(nullable,nonatomic, copy) UIImage *image; + +/** 主标题颜色 */ +@property(nonatomic, strong) UIColor *titleColor; +/** 主标题字体,默认18,加粗 */ +@property(nonatomic, strong) UIFont *titleFont; +/** 副标题颜色 */ +@property(nonatomic, strong) UIColor *messageColor; +/** 副标题字体,默认16,未加粗 */ +@property(nonatomic, strong) UIFont *messageFont; +/** 对齐方式(包括主标题和副标题) */ +@property(nonatomic, assign) NSTextAlignment textAlignment; +/** 头部图标的限制大小,默认无穷大 */ +@property (nonatomic, assign) CGSize imageLimitSize; +/** 图片的tintColor,当外部的图片使用了UIImageRenderingModeAlwaysTemplate时,该属性可起到作用 */ +@property (nonatomic, strong) UIColor *imageTintColor; + +/* + * action水平排列还是垂直排列 + * actionSheet样式下:默认为UILayoutConstraintAxisVertical(垂直排列), 如果设置为UILayoutConstraintAxisHorizontal(水平排列),则除去取消样式action之外的其余action将水平排列 + * alert样式下:当actions的个数大于2,或者某个action的title显示不全时为UILayoutConstraintAxisVertical(垂直排列),否则默认为UILayoutConstraintAxisHorizontal(水平排列),此样式下设置该属性可以修改所有action的排列方式 + * 不论哪种样式,只要外界设置了该属性,永远以外界设置的优先 + */ +@property(nonatomic) UILayoutConstraintAxis actionAxis; + +/* 距离屏幕边缘的最小间距 + * alert样式下该属性是指对话框四边与屏幕边缘之间的距离,此样式下默认值随设备变化,actionSheet样式下是指弹出边的对立边与屏幕之间的距离,比如如果从右边弹出,那么该属性指的就是对话框左边与屏幕之间的距离,此样式下默认值为70 + */ +@property(nonatomic, assign) CGFloat minDistanceToEdges; + +/** SPAlertControllerStyleAlert样式下默认6.0f,SPAlertControllerStyleActionSheet样式下默认13.0f,去除半径设置为0即可 */ +@property(nonatomic, assign) CGFloat cornerRadius; + +/** 对话框的偏移量,y值为正向下偏移,为负向上偏移;x值为正向右偏移,为负向左偏移,该属性只对SPAlertControllerStyleAlert样式有效,键盘的frame改变会自动偏移,如果手动设置偏移只会取手动设置的 */ +@property(nonatomic, assign) CGPoint offsetForAlert; +/** 设置alert样式下的偏移量,动画为NO则跟属性offsetForAlert等效 */ +- (void)setOffsetForAlert:(CGPoint)offsetForAlert animated:(BOOL)animated; + +/** 是否需要对话框拥有毛玻璃,默认为YES */ +@property(nonatomic, assign) BOOL needDialogBlur; + +/** 是否单击背景退出对话框,默认为YES */ +@property(nonatomic, assign) BOOL tapBackgroundViewDismiss; + +@property(nonatomic, weak) id delegate; + +@property(nonatomic, readonly) SPAlertControllerStyle preferredStyle; +@property(nonatomic, readonly) SPAlertAnimationType animationType; + +/** 设置action与下一个action之间的间距, action仅限于非取消样式,必须在'-addAction:'之后设置,iOS11或iOS11以上才能使用 */ +- (void)setCustomSpacing:(CGFloat)spacing afterAction:(SPAlertAction *)action API_AVAILABLE(ios(11.0)); +- (CGFloat)customSpacingAfterAction:(SPAlertAction *)action API_AVAILABLE(ios(11.0)); + +/** 设置蒙层的外观样式,可通过alpha调整透明度 */ +- (void)setBackgroundViewAppearanceStyle:(UIBlurEffectStyle)style alpha:(CGFloat)alpha; + +// 插入一个组件view,位置处于头部和action部分之间,要求头部和action部分同时存在 +- (void)insertComponentView:(nonnull UIView *)componentView; + + +// ---------------------------------------------- custom ----------------------------------------------------- +/** + 创建控制器(自定义整个对话框) + + @param customAlertView 整个对话框的自定义view + @param preferredStyle 对话框样式 + @param animationType 动画类型 + @return 控制器对象 + */ ++ (instancetype)alertControllerWithCustomAlertView:(nonnull UIView *)customAlertView preferredStyle:(SPAlertControllerStyle)preferredStyle animationType:(SPAlertAnimationType)animationType; +/** + 创建控制器(自定义对话框的头部) + + @param customHeaderView 头部自定义view + @param preferredStyle 对话框样式 + @param animationType 动画类型 + @return 控制器对象 + */ ++ (instancetype)alertControllerWithCustomHeaderView:(nonnull UIView *)customHeaderView preferredStyle:(SPAlertControllerStyle)preferredStyle animationType:(SPAlertAnimationType)animationType; +/** + 创建控制器(自定义对话框的action部分) + + @param customActionSequenceView action部分的自定义view + @param title 大标题 + @param message 副标题 + @param preferredStyle 对话框样式 + @param animationType 动画类型 + @return 控制器对象 + */ ++ (instancetype)alertControllerWithCustomActionSequenceView:(nonnull UIView *)customActionSequenceView title:(nullable NSString *)title message:(nullable NSString *)message preferredStyle:(SPAlertControllerStyle)preferredStyle animationType:(SPAlertAnimationType)animationType; + +/** 更新自定义view的size,比如屏幕旋转,自定义view的大小发生了改变,可通过该方法更新size */ +- (void)updateCustomViewSize:(CGSize)size; + +@property(nonatomic, assign) CGFloat cornerRadiusForAlert NS_DEPRECATED_IOS(8_0, 8_0,"Use cornerRadius instead"); +@property (nonatomic, assign) CGFloat maxTopMarginForActionSheet NS_DEPRECATED_IOS(8_0, 8_0,"Use minDistanceToEdges instead"); +@property(nonatomic, assign) CGFloat maxMarginForAlert NS_DEPRECATED_IOS(8_0, 8_0,"Use minDistanceToEdges instead"); +@property(nonatomic, assign) NSInteger maxNumberOfActionHorizontalArrangementForAlert NS_DEPRECATED_IOS(8_0, 8_0,"Use actionAxis instead"); +@property(nonatomic, assign) CGFloat offsetYForAlert NS_DEPRECATED_IOS(8_0, 8_0,"Use offsetForAlert instead"); ++ (instancetype)alertControllerWithTitle:(nullable NSString *)title message:(nullable NSString *)message preferredStyle:(SPAlertControllerStyle)preferredStyle animationType:(SPAlertAnimationType)animationType customView:(nullable UIView *)customView NS_DEPRECATED_IOS(8_0, 8_0,"Use +alertControllerWithCustomAlertView:preferredStyle:animationType:"); ++ (instancetype)alertControllerWithPreferredStyle:(SPAlertControllerStyle)preferredStyle animationType:(SPAlertAnimationType)animationType customHeaderView:(nullable UIView *)customHeaderView NS_DEPRECATED_IOS(8_0, 8_0,"Use +alertControllerWithCustomHeaderView:preferredStyle:animationType:"); ++ (instancetype)alertControllerWithTitle:(nullable NSString *)title message:(nullable NSString *)message preferredStyle:(SPAlertControllerStyle)preferredStyle animationType:(SPAlertAnimationType)animationType customCenterView:(nullable UIView *)customCenterView NS_DEPRECATED_IOS(8_0, 8_0,"Use -insertComponentView:"); ++ (instancetype)alertControllerWithTitle:(nullable NSString *)title message:(nullable NSString *)message preferredStyle:(SPAlertControllerStyle)preferredStyle animationType:(SPAlertAnimationType)animationType customFooterView:(nullable UIView *)customFooterView NS_DEPRECATED_IOS(8_0, 8_0,"Use +alertControllerWithCustomActionSequenceView:title:message:preferredStyle:animationType:"); +@end + +@protocol SPAlertControllerDelegate +@optional; +- (void)willPresentAlertController:(SPAlertController *)alertController; // 将要present +- (void)didPresentAlertController:(SPAlertController *)alertController; // 已经present +- (void)willDismissAlertController:(SPAlertController *)alertController; // 将要dismiss +- (void)didDismissAlertController:(SPAlertController *)alertController; // 已经dismiss + + +- (void)sp_alertControllerWillShow:(SPAlertController *)alertController NS_DEPRECATED_IOS(8_0, 8_0,"Use -willPresentAlertController:"); +- (void)sp_alertControllerDidShow:(SPAlertController *)alertController NS_DEPRECATED_IOS(8_0, 8_0,"Use -DidPresentAlertController:"); +- (void)sp_alertControllerWillHide:(SPAlertController *)alertController NS_DEPRECATED_IOS(8_0, 8_0,"Use -willDismissAlertController:"); +- (void)sp_alertControllerDidHide:(SPAlertController *)alertController NS_DEPRECATED_IOS(8_0, 8_0,"Use -DidDismissAlertController:"); +@end + +@interface SPAlertPresentationController : UIPresentationController +@end + +@interface SPAlertAnimation : NSObject ++ (instancetype)animationIsPresenting:(BOOL)presenting; +@end + +NS_ASSUME_NONNULL_END diff --git a/Ifish/SPAlertController/SPAlertController.m b/Ifish/SPAlertController/SPAlertController.m new file mode 100755 index 0000000..38b52f1 --- /dev/null +++ b/Ifish/SPAlertController/SPAlertController.m @@ -0,0 +1,2764 @@ +// +// 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 -------------------------------- + diff --git a/Ifish/Utinitys/IfishHttpRequest/IfishUserDataUnity.m b/Ifish/Utinitys/IfishHttpRequest/IfishUserDataUnity.m index de8a0e4..f4d7314 100644 --- a/Ifish/Utinitys/IfishHttpRequest/IfishUserDataUnity.m +++ b/Ifish/Utinitys/IfishHttpRequest/IfishUserDataUnity.m @@ -114,7 +114,9 @@ [IfishUserDefaultHelper chageLevlelGrad:gradeNum]; } //完成登陆验证开始 广告页倒计时 - [self setAdViewVC]; + // [self setAdViewVC]; + //去除广告 + [self setAppTabRoot]; } @@ -188,9 +190,23 @@ //设置根视图 -(void)setAppTabRoot{ + UserModel*model=[dataContorl getUserInfo]; + NSUserDefaults*userdefult=[NSUserDefaults standardUserDefaults]; + BOOL skiped =[userdefult boolForKey:[CommonUtils getNotNilStr:model.unionId]]; + if (!skiped&&[CommonUtils getNotNilStr:model.phoneNumber].length==0&&[CommonUtils getNotNilStr:model.unionId].length) + { - IfishMianTabViewController *mianVC=[[IfishMianTabViewController alloc] init]; - [UIApplication sharedApplication].delegate.window.rootViewController=mianVC; + RegistViewController*revv=[[RegistViewController alloc]init]; + revv.isBind=YES; + revv.isFromLogin=YES; + [UIApplication sharedApplication].delegate.window.rootViewController=revv;; + } + else + { + + IfishMianTabViewController *mianVC=[[IfishMianTabViewController alloc] init]; + [UIApplication sharedApplication].delegate.window.rootViewController=mianVC; + } } -(void)bindPhone diff --git a/Ifish/controllers/IfishTabControllers/设备/IfishDeviceSelectList/IfishBindDeviceSelectViewController.m b/Ifish/controllers/IfishTabControllers/设备/IfishDeviceSelectList/IfishBindDeviceSelectViewController.m index 1feb25c..dde3824 100644 --- a/Ifish/controllers/IfishTabControllers/设备/IfishDeviceSelectList/IfishBindDeviceSelectViewController.m +++ b/Ifish/controllers/IfishTabControllers/设备/IfishDeviceSelectList/IfishBindDeviceSelectViewController.m @@ -16,6 +16,7 @@ #import "HitbarWifeVc.h" #import "SVProgressHUD.h" #import "ConAquarMethodVC.h" +#import "ConfigWifiViewController.h" @interface IfishBindDeviceSelectViewController () @property (nonatomic,strong) UITableView *tableView; @@ -163,7 +164,7 @@ extern BOOL isfromCameraView; // // } self.hidesBottomBarWhenPushed = YES; - ConAquarMethodVC *methodVC = InitObject(ConAquarMethodVC); + ConfigWifiViewController *methodVC = [[ConfigWifiViewController alloc]init]; methodVC.deviceType=DEVICEAQUARIUM; // SecondConnectWifiController *wifivc=[[SecondConnectWifiController alloc]init]; // wifivc.vcTitle=@"连接水族箱"; diff --git a/Ifish/controllers/IfishYooseeFile/IfishYooseeHelper/Constants.h b/Ifish/controllers/IfishYooseeFile/IfishYooseeHelper/Constants.h index 6a924a4..83d92cf 100644 --- a/Ifish/controllers/IfishYooseeFile/IfishYooseeHelper/Constants.h +++ b/Ifish/controllers/IfishYooseeFile/IfishYooseeHelper/Constants.h @@ -200,6 +200,18 @@ typedef NS_ENUM(NSUInteger, SmartDeviceType) { DEVICEHEATING, DEVICEPETS }; +typedef NS_ENUM(NSUInteger, ConnectType) { + + ///腾讯airkiss + ConnectTypeAirKiss = 1, + + /// 智能配网 + ConnectTypeSmartESPTouch , + + /// 热点配网 + ConnectTypeAP + +}; #define Ali_Push_APPKey @"26007228" #define Ali_Push_APPSecret @"87c9206424399968ba11a56a9a979cfe" diff --git a/Ifish/controllers/leftcontrollers/ConfigWfiViewController.h b/Ifish/controllers/leftcontrollers/ConfigWifiViewController.h similarity index 69% rename from Ifish/controllers/leftcontrollers/ConfigWfiViewController.h rename to Ifish/controllers/leftcontrollers/ConfigWifiViewController.h index 562de11..df89788 100644 --- a/Ifish/controllers/leftcontrollers/ConfigWfiViewController.h +++ b/Ifish/controllers/leftcontrollers/ConfigWifiViewController.h @@ -9,33 +9,33 @@ #import "LXWaveProgressView.h" #import "StoreNameView.h" #import "EspTouchDelegateImpl.h" -@interface ConfigWfiViewController : BaseViewController -@property (weak, nonatomic) IBOutlet UIImageView *wifigifImgView; +@interface ConfigWifiViewController : BaseViewController + @property (weak, nonatomic) IBOutlet UILabel *wifiNamelabel; @property (weak, nonatomic) IBOutlet UITextField *wifiTextFiled; -@property (weak, nonatomic) IBOutlet UILabel *redTextLable; + @property (nonatomic, strong) UIView *mask; @property (nonatomic, strong) StoreNameView *storeNameView; @property (nonatomic, strong) NSString *storeName; - (IBAction)makeSureButton:(id)sender; -- (IBAction)lickHereBtnClicked:(id)sender; -@property (weak, nonatomic) IBOutlet UILabel *attentionLabel; +- (IBAction)connectTypeClick:(id)sender; +@property (weak, nonatomic) IBOutlet UILabel *connectTypeTitle; +@property (weak, nonatomic) IBOutlet UIButton *shuoMingShuBtn; @property(nonatomic,strong)NSString*ssid; @property(nonatomic,strong)NSString*wifiPass; @property(nonatomic,strong)NSString*bssid; @property(nonatomic,strong)NSString*deviceBssid; -@property (weak, nonatomic) IBOutlet UIButton *shareButton; -@property (weak, nonatomic) IBOutlet LXWaveProgressView *waveProgressView; +@property (weak, nonatomic) IBOutlet UIButton *connectTypeBtn; -@property (weak, nonatomic) IBOutlet UIButton *shuoMingShuBtn; -@property (weak, nonatomic) IBOutlet UIImageView *nodImg; @property (weak, nonatomic) IBOutlet UILabel *connectTipLbl; @property (weak, nonatomic) IBOutlet UIButton *connectBtn; @property(nonatomic,copy)NSString*vcTitle; //界面复用 设备类型 @property (assign, nonatomic) SmartDeviceType deviceType; +//配网配型 +@property (assign, nonatomic) ConnectType connectType; @end diff --git a/Ifish/controllers/leftcontrollers/ConfigWfiViewController.m b/Ifish/controllers/leftcontrollers/ConfigWifiViewController.m similarity index 93% rename from Ifish/controllers/leftcontrollers/ConfigWfiViewController.m rename to Ifish/controllers/leftcontrollers/ConfigWifiViewController.m index 69a08a0..6b06e6d 100644 --- a/Ifish/controllers/leftcontrollers/ConfigWfiViewController.m +++ b/Ifish/controllers/leftcontrollers/ConfigWifiViewController.m @@ -6,7 +6,8 @@ // Copyright © 2016年 imac. All rights reserved. // -#import "ConfigWfiViewController.h" +#import "SPAlertController.h" +#import "ConfigWifiViewController.h" #import #import "DeviceModel.h" #import "SaySomethingViewController.h" @@ -40,7 +41,7 @@ typedef NS_ENUM(NSInteger,lodingViewdissMissStyle) { lodingViewdissMissAlreadyBinded }; -@interface ConfigWfiViewController () +@interface ConfigWifiViewController () @property (nonatomic, strong) NSCondition *_condition; @property (atomic, strong) ESPTouchTask *_esptouchTask; @property (nonatomic, strong) EspTouchDelegateImpl *_esptouchDelegate; @@ -63,16 +64,17 @@ Strong CLLocationManager *locationManager;//iOS13,获取WiFi名称必须开启 //extern BOOL formLogIn;//连接页面是否来自登录界面 extern BOOL isfromCameraView; -@implementation ConfigWfiViewController +@implementation ConfigWifiViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view from its nib. self.view.backgroundColor=[UIColor whiteColor]; + self.connectType = ConnectTypeAirKiss; _deviceArry =[[NSMutableArray alloc]init]; - [self updateUI]; + //输入框在上面新UI 可去 _wifiTextFiled.delegate=self; _wifiTextFiled.layer.masksToBounds=YES; @@ -102,7 +104,7 @@ extern BOOL isfromCameraView; UIBarButtonItem *item = [[UIBarButtonItem alloc] initWithTitle:@"" style:UIBarButtonItemStylePlain target:nil action:nil]; self.navigationItem.backBarButtonItem = item; - [self.shuoMingShuBtn addTarget:self action:@selector(shuoMingShuBtnAction:) forControlEvents:UIControlEventTouchUpInside]; + [self creatConnectStateUI]; //获取WiFi名称 @@ -110,21 +112,7 @@ extern BOOL isfromCameraView; } -- (void)updateUI { - CGFloat yOffset = 0; - if (is_iPhone_X) { - yOffset = 20 + 88; - } else { - yOffset = 20 + 64; - } - self.nodImg.frame = CGRectMake(12, yOffset, 10, 10); - self.wifiNamelabel.frame = CGRectMake(23, yOffset - 18 + 5, 336, 36); - self.attentionLabel.frame=CGRectMake(30, CGRectGetMidY(self.waveProgressView.frame), kScreenWidth-60, 130); - [self.view bringSubviewToFront:self.attentionLabel]; - [self.view bringSubviewToFront:self.connectTipLbl]; - [self.view bringSubviewToFront:self.connectBtn]; - -} + -(void)viewWillAppear:(BOOL)animated{ [super viewWillAppear:animated]; @@ -137,7 +125,42 @@ extern BOOL isfromCameraView; } #pragma mark -连接不上? --(void)shuoMingShuBtnAction:(UIButton *)btn{ +-(void)connectTypeClick:(UIButton *)btn +{ + WEAK_SELF; + NSString*airkisTitle = @"一键联网"; + NSString*smartTitle = @"快捷联网"; + NSString*apTitle = @"AP联网"; + SPAlertController*alert =[SPAlertController alertControllerWithTitle:nil message:nil preferredStyle:SPAlertControllerStyleActionSheet animationType:SPAlertAnimationTypeDefault]; + SPAlertAction*airkiss =[SPAlertAction actionWithTitle:airkisTitle style:SPAlertActionStyleDefault handler:^(SPAlertAction * _Nonnull action) { + weakSelf.connectType = ConnectTypeAirKiss; + weakSelf.connectTypeTitle.text = airkisTitle; + }]; + + + SPAlertAction*smart =[SPAlertAction actionWithTitle:smartTitle style:SPAlertActionStyleDefault handler:^(SPAlertAction * _Nonnull action) { + weakSelf.connectType = ConnectTypeSmartESPTouch; + weakSelf.connectTypeTitle.text = smartTitle; + }]; + + SPAlertAction*ap =[SPAlertAction actionWithTitle:apTitle style:SPAlertActionStyleDefault handler:^(SPAlertAction * _Nonnull action) { + weakSelf.connectType = ConnectTypeSmartESPTouch; + weakSelf.connectTypeTitle.text = apTitle; + }]; + SPAlertAction*cancel =[SPAlertAction actionWithTitle:@"取消" style:SPAlertActionStyleCancel handler:^(SPAlertAction * _Nonnull action) { + + }]; + + [alert addAction:airkiss]; + [alert addAction:smart]; + [alert addAction:ap]; + [alert addAction:cancel]; + + [self presentViewController:alert animated:YES completion:nil]; + +} + +-(IBAction)shuoMingShuBtnAction:(UIButton *)btn{ // PushMasssageWebViewController*webVC=[[PushMasssageWebViewController alloc]init]; // webVC.pushlink =IFISH_DEVCEITROURL; @@ -153,7 +176,6 @@ extern BOOL isfromCameraView; } - -(void)creatConnectStateUI{ [self connectNormalView]; @@ -172,7 +194,7 @@ extern BOOL isfromCameraView; yOffset = 64; } - IfishConnectingView *connectingView=[[IfishConnectingView alloc] initWithFrame:CGRectMake(0,yOffset, kScreenSize.width, CGRectGetMaxY(self.wifiTextFiled.frame))]; + IfishConnectingView *connectingView=[[IfishConnectingView alloc] initWithFrame:self.connectTipLbl.frame]; self.connectingView =connectingView; @@ -184,9 +206,9 @@ extern BOOL isfromCameraView; self.wifiTextFiled.hidden = NO; self.connectTipLbl.hidden = NO; self.connectBtn.hidden = NO; - self.shareButton.hidden = NO; - self.nodImg.hidden = NO; - self.redTextLable.hidden=NO; + self.connectTypeBtn.hidden = NO; + + }else{ @@ -202,9 +224,9 @@ extern BOOL isfromCameraView; self.wifiTextFiled.hidden = NO; self.connectTipLbl.hidden = NO; self.connectBtn.hidden = NO; - self.shareButton.hidden = NO; - self.nodImg.hidden = NO; - self.redTextLable.hidden=NO; + self.connectTypeBtn.hidden = NO; + + }]; } @@ -225,13 +247,12 @@ extern BOOL isfromCameraView; }]; - self.wifiNamelabel.hidden = YES; - self.wifiTextFiled.hidden = YES; +// self.wifiNamelabel.hidden = YES; +// self.wifiTextFiled.hidden = YES; self.connectTipLbl.hidden = YES; self.connectBtn.hidden = YES; - self.shareButton.hidden = YES; - self.nodImg.hidden = YES; - self.redTextLable.hidden=YES; + self.connectTypeBtn.hidden = YES; + } @@ -262,8 +283,8 @@ extern BOOL isfromCameraView; CGFloat LabelTitleH = 20; _maclabel.frame = CGRectMake(kScreenSize.width/2-LabelTitleW/2,CGRectGetMaxY(self.wifiTextFiled.frame)+20, LabelTitleW, LabelTitleH); - [self.view addSubview:_maclabel]; - [self.view bringSubviewToFront:_maclabel]; +// [self.view addSubview:_maclabel]; +// [self.view bringSubviewToFront:_maclabel]; self.bakbutton.userInteractionEnabled=YES; [self connectNormalView]; @@ -352,13 +373,7 @@ extern BOOL isfromCameraView; } -(void)initWifiImage{ - dispatch_async(dispatch_get_main_queue(), ^{ - - self.wifigifImgView.animationImages=[NSArray arrayWithObjects:[UIImage imageNamed:@"fish_png_0"],[UIImage imageNamed:@"fish_png_1"],[UIImage imageNamed:@"fish_png_2"],[UIImage imageNamed:@"fish_png_3"], nil]; - self.wifigifImgView.animationDuration=2; - self.wifigifImgView.animationRepeatCount=0; - [self.wifigifImgView startAnimating]; - }); + } /* @@ -480,7 +495,8 @@ extern BOOL isfromCameraView; [attributedString addAttribute:NSForegroundColorAttributeName value:[UIColor colorWithRed:0 green:170.0/255.0 blue:218.0/255.0 alpha:1] range:NSMakeRange(msg.length - 3 -_ssid.length,_ssid.length)]; [attributedString addAttribute:NSParagraphStyleAttributeName value:paraghStyle range:NSMakeRange(0, msg.length)]; - [_wifiNamelabel setAttributedText:attributedString]; + // [_wifiNamelabel setAttributedText:attributedString]; + _wifiNamelabel.text=_ssid; _wifiTextFiled.secureTextEntry=NO; _wifiTextFiled.text=pass; _wifiTextFiled.clearButtonMode=UITextFieldViewModeAlways; @@ -874,7 +890,7 @@ extern BOOL isfromCameraView; CGRect keyboardFrame = [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue]; - CGRect buttonRect = [self.shareButton convertRect:self.shareButton.bounds toView:self.view]; + CGRect buttonRect = [self.connectBtn convertRect:self.connectBtn.bounds toView:self.view]; CGFloat buttonMargin =(buttonRect.origin.y + buttonRect.size.height + 10); diff --git a/Ifish/controllers/leftcontrollers/ConfigWfiViewController.xib b/Ifish/controllers/leftcontrollers/ConfigWifiViewController.xib similarity index 59% rename from Ifish/controllers/leftcontrollers/ConfigWfiViewController.xib rename to Ifish/controllers/leftcontrollers/ConfigWifiViewController.xib index a5debc4..11c8a4d 100644 --- a/Ifish/controllers/leftcontrollers/ConfigWfiViewController.xib +++ b/Ifish/controllers/leftcontrollers/ConfigWifiViewController.xib @@ -9,9 +9,16 @@ - + + + + + + + + @@ -19,8 +26,8 @@ - + + + + - + - + - - - + + + - + + + + + + + + + diff --git a/Ifish/controllers/leftcontrollers/SecondConnectWifiController.xib b/Ifish/controllers/leftcontrollers/SecondConnectWifiController.xib index 52c9ba2..57e045c 100644 --- a/Ifish/controllers/leftcontrollers/SecondConnectWifiController.xib +++ b/Ifish/controllers/leftcontrollers/SecondConnectWifiController.xib @@ -1,11 +1,9 @@ - - - - + + - + @@ -45,7 +43,7 @@ - @@ -129,8 +127,8 @@ - - - + + +