SPAlertController.m 142 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764
  1. //
  2. // SPAlertController.m
  3. // SPAlertController
  4. //
  5. // Created by 乐升平 on 18/10/12. https://github.com/SPStore/SPAlertController
  6. // Copyright © 2018-2019 leshengping (lesp163@163.com). All rights reserved.
  7. //
  8. #import "SPAlertController.h"
  9. #define SP_SCREEN_WIDTH [UIScreen mainScreen].bounds.size.width
  10. #define SP_SCREEN_HEIGHT [UIScreen mainScreen].bounds.size.height
  11. #define SP_LINE_WIDTH 1.0 / [UIScreen mainScreen].scale
  12. #define Is_iPhoneX MAX(SP_SCREEN_WIDTH, SP_SCREEN_HEIGHT) >= 812
  13. #define SP_STATUS_BAR_HEIGHT (Is_iPhoneX ? 44 : 20)
  14. #define SP_ACTION_TITLE_FONTSIZE 18
  15. #define SP_ACTION_HEIGHT 55.0
  16. @interface SPColorStyle : NSObject
  17. + (UIColor *)normalColor;
  18. + (UIColor *)selectedColor;
  19. + (UIColor *)lineColor;
  20. + (UIColor *)line2Color;
  21. + (UIColor *)lightLineColor;
  22. + (UIColor *)darkLineColor;
  23. + (UIColor *)lightWhite_DarkBlackColor;
  24. + (UIColor *)lightBlack_DarkWhiteColor;
  25. + (UIColor *)textViewBackgroundColor;
  26. + (UIColor *)alertRedColor;
  27. + (UIColor *)grayColor;
  28. + (UIColor *)colorPairsWithDynamicLightColor:(UIColor *)lightColor darkColor:(UIColor *)darkColor;
  29. + (UIColor *)colorPairsWithStaticLightColor:(UIColor *)lightColor darkColor:(UIColor *)darkColor;
  30. @end
  31. @implementation SPColorStyle
  32. + (UIColor *)normalColor {
  33. return [self colorPairsWithDynamicLightColor:[[UIColor whiteColor] colorWithAlphaComponent:0.7]
  34. darkColor:[UIColor colorWithRed:44.0 / 255.0 green:44.0 / 255.0 blue:44.0 / 255.0 alpha:1.0]];
  35. }
  36. + (UIColor *)selectedColor {
  37. return [self colorPairsWithDynamicLightColor:[[UIColor grayColor] colorWithAlphaComponent:0.1]
  38. darkColor:[UIColor colorWithRed:55.0 / 255.0 green:55.0 / 255.0 blue:55.0 / 255.0 alpha:1.0]];
  39. }
  40. + (UIColor *)lineColor {
  41. return [self colorPairsWithDynamicLightColor:[self lightLineColor]
  42. darkColor:[self darkLineColor]];
  43. }
  44. + (UIColor *)line2Color {
  45. return [self colorPairsWithDynamicLightColor:[[UIColor grayColor] colorWithAlphaComponent:0.15]
  46. darkColor:[UIColor colorWithRed:29.0 / 255.0 green:29.0 / 255.0 blue:29.0 / 255.0 alpha:1.0]];
  47. }
  48. + (UIColor *)lightWhite_DarkBlackColor {
  49. return [self colorPairsWithDynamicLightColor:[UIColor whiteColor]
  50. darkColor:[UIColor blackColor]];
  51. }
  52. + (UIColor *)lightBlack_DarkWhiteColor {
  53. return [self colorPairsWithDynamicLightColor:[UIColor blackColor]
  54. darkColor:[UIColor whiteColor]];
  55. }
  56. + (UIColor *)lightLineColor {
  57. return [[UIColor grayColor] colorWithAlphaComponent:0.3];
  58. }
  59. + (UIColor *)darkLineColor {
  60. return [UIColor colorWithRed:60.0 / 255.0 green:60.0 / 255.0 blue:60.0 / 255.0 alpha:1.0];
  61. }
  62. + (UIColor *)textViewBackgroundColor {
  63. return [self colorPairsWithDynamicLightColor:[UIColor colorWithRed:247.0 / 255.0 green:247.0 / 255.0 blue:247.0 / 255.0 alpha:1.0]
  64. darkColor:[UIColor colorWithRed:54.0 / 255.0 green:54.0 / 255.0 blue:54.0 / 255.0 alpha:1.0]];
  65. }
  66. + (UIColor *)alertRedColor {
  67. return [UIColor systemRedColor];
  68. }
  69. + (UIColor *)grayColor {
  70. return [UIColor grayColor];
  71. }
  72. + (UIColor *)colorPairsWithDynamicLightColor:(UIColor *)lightColor darkColor:(UIColor *)darkColor {
  73. if (@available(iOS 13.0, *)) {
  74. return [UIColor colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull traitCollection) {
  75. if(traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {
  76. return darkColor;
  77. } else {
  78. return lightColor;
  79. }
  80. }];
  81. } else {
  82. return lightColor;
  83. }
  84. }
  85. + (UIColor *)colorPairsWithStaticLightColor:(UIColor *)lightColor darkColor:(UIColor *)darkColor {
  86. if (@available(iOS 13.0, *)) {
  87. UIUserInterfaceStyle mode = UITraitCollection.currentTraitCollection.userInterfaceStyle;
  88. if (mode == UIUserInterfaceStyleDark) {
  89. return darkColor;
  90. } else if (mode == UIUserInterfaceStyleLight) {
  91. return lightColor;
  92. } else {
  93. return lightColor;
  94. }
  95. }
  96. return lightColor;
  97. }
  98. @end
  99. #pragma mark ---------------------------- SPAlertAction begin --------------------------------
  100. #pragma clang diagnostic push
  101. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  102. @interface SPAlertAction()
  103. @property (nonatomic, assign) SPAlertActionStyle style;
  104. @property (nonatomic, copy) void (^handler)(SPAlertAction *action);
  105. // 当在addAction之后设置action属性时,会回调这个block,设置相应控件的字体、颜色等
  106. // 如果没有这个block,那使用时,只有在addAction之前设置action的属性才有效
  107. @property (nonatomic, copy) void (^propertyChangedBlock)(SPAlertAction *action, BOOL needUpdateConstraints);
  108. @end
  109. @implementation SPAlertAction
  110. // 由于要对装载action的数组进行拷贝,所以SPAlertAction也需要支持拷贝
  111. - (id)copyWithZone:(NSZone *)zone {
  112. SPAlertAction *action = [[[self class] alloc] init];
  113. action.title = self.title;
  114. action.attributedTitle = self.attributedTitle;
  115. action.image = self.image;
  116. action.imageTitleSpacing = self.imageTitleSpacing;
  117. action.style = self.style;
  118. action.enabled = self.enabled;
  119. action.titleColor = self.titleColor;
  120. action.titleFont = self.titleFont;
  121. action.titleEdgeInsets = self.titleEdgeInsets;
  122. action.handler = self.handler;
  123. action.propertyChangedBlock = self.propertyChangedBlock;
  124. return action;
  125. }
  126. + (instancetype)actionWithTitle:(nullable NSString *)title style:(SPAlertActionStyle)style handler:(void (^ __nullable)(SPAlertAction *action))handler {
  127. SPAlertAction *action = [[self alloc] initWithTitle:title style:(SPAlertActionStyle)style handler:handler];
  128. return action;
  129. }
  130. - (instancetype)initWithTitle:(nullable NSString *)title style:(SPAlertActionStyle)style handler:(void (^ __nullable)(SPAlertAction *action))handler {
  131. self = [self init];
  132. self.title = title;
  133. self.style = style;
  134. self.handler = handler;
  135. if (style == SPAlertActionStyleDestructive) {
  136. self.titleColor = [SPColorStyle alertRedColor];
  137. self.titleFont = [UIFont systemFontOfSize:SP_ACTION_TITLE_FONTSIZE];
  138. } else if (style == SPAlertActionStyleCancel) {
  139. self.titleColor = [SPColorStyle lightBlack_DarkWhiteColor];
  140. self.titleFont = [UIFont boldSystemFontOfSize:SP_ACTION_TITLE_FONTSIZE];
  141. } else {
  142. self.titleColor = [SPColorStyle lightBlack_DarkWhiteColor];
  143. self.titleFont = [UIFont systemFontOfSize:SP_ACTION_TITLE_FONTSIZE];
  144. }
  145. return self;
  146. }
  147. - (instancetype)init {
  148. if (self = [super init]) {
  149. [self initialize];
  150. }
  151. return self;
  152. }
  153. - (void)initialize {
  154. _enabled = YES; // 默认能点击
  155. _titleColor = [SPColorStyle lightBlack_DarkWhiteColor];
  156. _titleFont = [UIFont systemFontOfSize:SP_ACTION_TITLE_FONTSIZE];
  157. _titleEdgeInsets = UIEdgeInsetsMake(0, 15, 0, 15);
  158. }
  159. - (void)setTitle:(NSString *)title {
  160. _title = title;
  161. if (self.propertyChangedBlock) {
  162. self.propertyChangedBlock(self, YES);
  163. }
  164. }
  165. - (void)setAttributedTitle:(NSAttributedString *)attributedTitle {
  166. _attributedTitle = attributedTitle;
  167. if (self.propertyChangedBlock) {
  168. self.propertyChangedBlock(self, YES);
  169. }
  170. }
  171. - (void)setImage:(UIImage *)image {
  172. _image = image;
  173. if (self.propertyChangedBlock) {
  174. self.propertyChangedBlock(self, YES);
  175. }
  176. }
  177. - (void)setImageTitleSpacing:(CGFloat)imageTitleSpacing {
  178. _imageTitleSpacing = imageTitleSpacing;
  179. if (self.propertyChangedBlock) {
  180. self.propertyChangedBlock(self, YES);
  181. }
  182. }
  183. - (void)setTitleColor:(UIColor *)titleColor {
  184. _titleColor = titleColor;
  185. if (self.propertyChangedBlock) {
  186. self.propertyChangedBlock(self,NO); // 颜色改变不需要更新布局
  187. }
  188. }
  189. - (void)setTitleFont:(UIFont *)titleFont {
  190. _titleFont = titleFont;
  191. if (self.propertyChangedBlock) {
  192. self.propertyChangedBlock(self,YES); // 字体改变需要更新布局
  193. }
  194. }
  195. - (void)setEnabled:(BOOL)enabled {
  196. _enabled = enabled;
  197. if (self.propertyChangedBlock) {
  198. self.propertyChangedBlock(self,NO); // enabled改变不需要更新布局
  199. }
  200. }
  201. @end
  202. #pragma mark ---------------------------- SPAlertAction end ----------------------------
  203. #pragma mark ---------------------------- SPInterfaceActionItemSeparatorView begin --------------------------------
  204. @interface SPInterfaceActionItemSeparatorView : UIView
  205. @end
  206. @implementation SPInterfaceActionItemSeparatorView
  207. - (instancetype)init {
  208. if (self = [super init]) {
  209. self.backgroundColor = [SPColorStyle lineColor];
  210. }
  211. return self;
  212. }
  213. - (void)layoutSubviews {
  214. [super layoutSubviews];
  215. self.backgroundColor = MIN(self.frame.size.width, self.frame.size.height) > SP_LINE_WIDTH ? [SPColorStyle line2Color] : [SPColorStyle lineColor];
  216. }
  217. @end
  218. #pragma mark ---------------------------- SPAlertControllerActionView end --------------------------------
  219. #pragma mark ---------------------------- SPInterfaceHeaderScrollView begin ----------------------------
  220. @interface SPInterfaceHeaderScrollView : UIScrollView
  221. @property (nonatomic, weak) UIView *contentView;
  222. @property (nonatomic, weak) UILabel *titleLabel;
  223. @property (nonatomic, weak) UILabel *messageLabel;
  224. @property (nonatomic, weak) UIImageView *imageView;
  225. @property (nonatomic, assign) CGSize imageLimitSize;
  226. @property (nonatomic, weak) UIStackView *textFieldView;
  227. @property (nonatomic, strong) NSMutableArray *textFields;
  228. @property (nonatomic, assign) UIEdgeInsets contentEdgeInsets;
  229. @property (nonatomic, copy) void(^headerViewSfeAreaDidChangBlock)(void);
  230. @end
  231. @implementation SPInterfaceHeaderScrollView
  232. - (instancetype)initWithFrame:(CGRect)frame {
  233. if (self = [super initWithFrame:frame]) {
  234. self.showsHorizontalScrollIndicator = NO;
  235. if (@available(iOS 11.0, *)) {
  236. self.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
  237. }
  238. self.contentEdgeInsets = UIEdgeInsetsMake(20, 15, 20, 15);
  239. }
  240. return self;
  241. }
  242. - (void)addTextField:(UITextField *)textField {
  243. [self.textFields addObject:textField];
  244. // 将textView添加到self.textFieldView中的布局队列中,UIStackView会根据设置的属性自动布局
  245. [self.textFieldView addArrangedSubview:textField];
  246. // 由于self.textFieldView是没有高度的,它的高度由子控件撑起,所以子控件必须要有高度
  247. [[NSLayoutConstraint constraintWithItem:textField attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:30.0f] setActive:YES];
  248. [self setNeedsUpdateConstraints];
  249. }
  250. - (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection {
  251. [super traitCollectionDidChange:previousTraitCollection];
  252. if (@available(iOS 13.0, *)) {
  253. // 设置CGColor,不要传previousTraitCollection,previousTraitCollection指的是上一次的模式
  254. UIColor *resolvedColor = [[SPColorStyle lineColor] resolvedColorWithTraitCollection:self.traitCollection];
  255. for (UITextField *textField in self.textFields) {
  256. textField.layer.borderColor = resolvedColor.CGColor;
  257. }
  258. }
  259. }
  260. - (NSMutableArray *)textFields {
  261. if (!_textFields) {
  262. _textFields = [[NSMutableArray alloc] init];
  263. }
  264. return _textFields;
  265. }
  266. - (void)safeAreaInsetsDidChange {
  267. [super safeAreaInsetsDidChange];
  268. CGFloat safeTop = self.safeAreaInsets.top < 20 ? 20 : self.safeAreaInsets.top+10;
  269. CGFloat safeLeft = self.safeAreaInsets.left < 15 ? 15 : self.safeAreaInsets.left;
  270. CGFloat safeBottom = self.safeAreaInsets.bottom < 20 ? 20 : self.safeAreaInsets.bottom+6;
  271. CGFloat safeRight = self.safeAreaInsets.right < 15 ? 15 : self.safeAreaInsets.right;
  272. _contentEdgeInsets = UIEdgeInsetsMake(safeTop, safeLeft, safeBottom, safeRight);
  273. // 这个block,主要是更新Label的最大预估宽度
  274. if (self.headerViewSfeAreaDidChangBlock) {
  275. self.headerViewSfeAreaDidChangBlock();
  276. }
  277. [self setNeedsUpdateConstraints];
  278. }
  279. - (void)updateConstraints {
  280. [super updateConstraints];
  281. UIView *contentView = self.contentView;
  282. // 对contentView布局
  283. // 先移除旧约束,再添加新约束
  284. [NSLayoutConstraint deactivateConstraints:self.constraints];
  285. [NSLayoutConstraint deactivateConstraints:contentView.constraints];
  286. [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[contentView]-0-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(contentView)]];
  287. [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[contentView]-0-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(contentView)]];
  288. [[NSLayoutConstraint constraintWithItem:contentView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeWidth multiplier:1.0 constant:0] setActive:YES];
  289. NSLayoutConstraint *equalHeightConstraint = [NSLayoutConstraint constraintWithItem:contentView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeHeight multiplier:1.0 constant:0];
  290. equalHeightConstraint.priority = 998.0f; // 优先级不能最高, 最顶层的父view有高度限制,如果子控件撑起后的高度大于限制高度,则scrollView滑动查看全部内容
  291. equalHeightConstraint.active = YES;
  292. UIImageView *imageView = _imageView;
  293. UIStackView *textFieldView = _textFieldView;
  294. CGFloat leftMargin = self.contentEdgeInsets.left;
  295. CGFloat rightMargin = self.contentEdgeInsets.right;
  296. CGFloat topMargin = self.contentEdgeInsets.top;
  297. CGFloat bottomMargin = self.contentEdgeInsets.bottom;
  298. // 对iconView布局
  299. if (imageView.image) {
  300. NSMutableArray *imageViewConstraints = [NSMutableArray array];
  301. [imageViewConstraints addObject:[NSLayoutConstraint constraintWithItem:imageView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.f constant:MIN(imageView.image.size.width, _imageLimitSize.width)]];
  302. [imageViewConstraints addObject:[NSLayoutConstraint constraintWithItem:imageView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.f constant:MIN(imageView.image.size.height, _imageLimitSize.height)]];
  303. [imageViewConstraints addObject:[NSLayoutConstraint constraintWithItem:imageView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:contentView attribute:NSLayoutAttributeCenterX multiplier:1.f constant:0]];
  304. [imageViewConstraints addObject:[NSLayoutConstraint constraintWithItem:imageView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:contentView attribute:NSLayoutAttributeTop multiplier:1.f constant:topMargin]];
  305. if (_titleLabel.text.length || _titleLabel.attributedText.length) {
  306. [imageViewConstraints addObject:[NSLayoutConstraint constraintWithItem:imageView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:_titleLabel attribute:NSLayoutAttributeTop multiplier:1.f constant:-17]];
  307. } else if (_messageLabel.text.length || _messageLabel.attributedText.length) {
  308. [imageViewConstraints addObject:[NSLayoutConstraint constraintWithItem:imageView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:_messageLabel attribute:NSLayoutAttributeTop multiplier:1.f constant:-17]];
  309. } else if (_textFields.count) {
  310. [imageViewConstraints addObject:[NSLayoutConstraint constraintWithItem:imageView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:textFieldView attribute:NSLayoutAttributeTop multiplier:1.f constant:-17]];
  311. } else {
  312. [imageViewConstraints addObject:[NSLayoutConstraint constraintWithItem:imageView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:contentView attribute:NSLayoutAttributeBottom multiplier:1.f constant:-bottomMargin]];
  313. }
  314. [NSLayoutConstraint activateConstraints:imageViewConstraints];
  315. }
  316. // 对titleLabel和messageLabel布局
  317. NSMutableArray *titleLabelConstraints = [NSMutableArray array];
  318. NSMutableArray *labels = [NSMutableArray array];
  319. if (_titleLabel.text.length || _titleLabel.attributedText.length) {
  320. [labels insertObject:_titleLabel atIndex:0];
  321. }
  322. if (_messageLabel.text.length || _messageLabel.attributedText.length) {
  323. [labels addObject:_messageLabel];
  324. }
  325. [labels enumerateObjectsUsingBlock:^(UILabel *label, NSUInteger idx, BOOL * _Nonnull stop) {
  326. // 左右间距
  327. [titleLabelConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:[NSString stringWithFormat:@"H:|-(==leftMargin)-[label]-(==rightMargin)-|"] options:0 metrics:@{@"leftMargin":@(leftMargin),@"rightMargin":@(rightMargin)} views:NSDictionaryOfVariableBindings(label)]];
  328. // 第一个子控件顶部间距
  329. if (idx == 0) {
  330. if (!imageView.image) {
  331. [titleLabelConstraints addObject:[NSLayoutConstraint constraintWithItem:label attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:contentView attribute:NSLayoutAttributeTop multiplier:1.f constant:topMargin]];
  332. }
  333. }
  334. // 最后一个子控件底部间距
  335. if (idx == labels.count - 1) {
  336. if (self.textFields.count) {
  337. [titleLabelConstraints addObject:[NSLayoutConstraint constraintWithItem:label attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:textFieldView attribute:NSLayoutAttributeTop multiplier:1.f constant:-bottomMargin]];
  338. } else {
  339. [titleLabelConstraints addObject:[NSLayoutConstraint constraintWithItem:label attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:contentView attribute:NSLayoutAttributeBottom multiplier:1.f constant:-bottomMargin]];
  340. }
  341. }
  342. // 子控件之间的垂直间距
  343. if (idx > 0) {
  344. [titleLabelConstraints addObject:[NSLayoutConstraint constraintWithItem:label attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:labels[idx - 1] attribute:NSLayoutAttributeBottom multiplier:1.f constant:7.5]];
  345. }
  346. }];
  347. [NSLayoutConstraint activateConstraints:titleLabelConstraints];
  348. if (self.textFields.count) {
  349. NSMutableArray *textFieldViewConstraints = [NSMutableArray array];
  350. if (!labels.count && !imageView.image) { // 没有titleLabel、messageLabel和iconView,textFieldView的顶部相对contentView,否则不用写,因为前面写好了
  351. [textFieldViewConstraints addObject:[NSLayoutConstraint constraintWithItem:textFieldView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:contentView attribute:NSLayoutAttributeTop multiplier:1.f constant:topMargin]];
  352. }
  353. [textFieldViewConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:[NSString stringWithFormat:@"H:|-(==leftMargin)-[textFieldView]-(==rightMargin)-|"] options:0 metrics:@{@"leftMargin":@(leftMargin),@"rightMargin":@(rightMargin)} views:NSDictionaryOfVariableBindings(textFieldView)]];
  354. [textFieldViewConstraints addObject:[NSLayoutConstraint constraintWithItem:textFieldView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:contentView attribute:NSLayoutAttributeBottom multiplier:1.f constant:-bottomMargin]];
  355. [NSLayoutConstraint activateConstraints:textFieldViewConstraints];
  356. }
  357. // systemLayoutSizeFittingSize:方法获取子控件撑起contentView后的高度,如果子控件是UILabel,那么子label必须设置preferredMaxLayoutWidth,否则当label多行文本时计算不准确
  358. NSLayoutConstraint *contentViewHeightConstraint = [NSLayoutConstraint constraintWithItem:contentView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:[contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height];
  359. contentViewHeightConstraint.active = YES;
  360. }
  361. - (UIView *)contentView {
  362. if (!_contentView) {
  363. UIView *contentView = [[UIView alloc] init];
  364. contentView.translatesAutoresizingMaskIntoConstraints = NO;
  365. [self addSubview:contentView];
  366. _contentView = contentView;
  367. }
  368. return _contentView;
  369. }
  370. - (UILabel *)titleLabel {
  371. if (!_titleLabel) {
  372. UILabel *titleLabel = [[UILabel alloc] init];
  373. titleLabel.font = [UIFont boldSystemFontOfSize:18];
  374. titleLabel.textAlignment = NSTextAlignmentCenter;
  375. titleLabel.textColor = [SPColorStyle lightBlack_DarkWhiteColor];
  376. titleLabel.numberOfLines = 0;
  377. titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
  378. [self.contentView addSubview:titleLabel];
  379. _titleLabel = titleLabel;
  380. }
  381. return _titleLabel;
  382. }
  383. - (UILabel *)messageLabel {
  384. if (!_messageLabel) {
  385. UILabel *messageLabel = [[UILabel alloc] init];
  386. messageLabel.font = [UIFont systemFontOfSize:18];
  387. messageLabel.textAlignment = NSTextAlignmentCenter;
  388. messageLabel.textColor = [SPColorStyle grayColor];
  389. messageLabel.numberOfLines = 0;
  390. messageLabel.translatesAutoresizingMaskIntoConstraints = NO;
  391. [self.contentView addSubview:messageLabel];
  392. _messageLabel = messageLabel;
  393. }
  394. return _messageLabel;
  395. }
  396. - (UIImageView *)imageView {
  397. if (!_imageView) {
  398. UIImageView *imageView = [[UIImageView alloc] init];
  399. imageView.translatesAutoresizingMaskIntoConstraints = NO;
  400. [self.contentView insertSubview:imageView atIndex:0];
  401. _imageView = imageView;
  402. }
  403. return _imageView;
  404. }
  405. - (UIStackView *)textFieldView {
  406. if (!_textFieldView) {
  407. UIStackView *textFieldView = [[UIStackView alloc] init];
  408. textFieldView.translatesAutoresizingMaskIntoConstraints = NO;
  409. textFieldView.distribution = UIStackViewDistributionFillEqually;
  410. textFieldView.axis = UILayoutConstraintAxisVertical;
  411. if (self.textFields.count) {
  412. [self.contentView addSubview:textFieldView];
  413. }
  414. _textFieldView = textFieldView;
  415. }
  416. return _textFieldView;
  417. }
  418. @end
  419. #pragma mark ---------------------------- SPInterfaceHeaderScrollView end ----------------------------
  420. #pragma mark ---------------------------- SPAlertControllerActionView begin --------------------------------
  421. @interface SPAlertControllerActionView : UIView
  422. @property (nonatomic, weak) id target;
  423. @property (nonatomic, assign) SEL methodAction;
  424. @property (nonatomic, strong) SPAlertAction *action;
  425. @property (nonatomic, weak) UIButton *actionButton;
  426. @property (nonatomic, strong) NSMutableArray *actionButtonConstraints;
  427. @property (nonatomic, assign) CGFloat afterSpacing;
  428. - (void)addTarget:(id)target action:(SEL)action;
  429. @end
  430. @implementation SPAlertControllerActionView
  431. - (instancetype)init {
  432. if (self = [super init]) {
  433. _afterSpacing = SP_LINE_WIDTH;
  434. }
  435. return self;
  436. }
  437. - (void)setAction:(SPAlertAction *)action {
  438. _action = action;
  439. self.actionButton.titleLabel.font = action.titleFont;
  440. if (action.enabled) {
  441. [self.actionButton setTitleColor:action.titleColor forState:UIControlStateNormal];
  442. } else {
  443. [self.actionButton setTitleColor:[action.titleColor colorWithAlphaComponent:0.4] forState:UIControlStateNormal];
  444. }
  445. // 注意不能赋值给按钮的titleEdgeInsets,当只有文字时,按钮的titleEdgeInsets设置top和bottom值无效
  446. self.actionButton.contentEdgeInsets = action.titleEdgeInsets;
  447. self.actionButton.enabled = action.enabled;
  448. self.actionButton.tintColor = action.tintColor;
  449. if (action.attributedTitle) {
  450. // 这里之所以要设置按钮颜色为黑色,是因为如果外界在addAction:之后设置按钮的富文本,那么富文本的颜色在没有采用NSForegroundColorAttributeName的情况下会自动读取按钮上普通文本的颜色,在addAction:之前设置会保持默认色(黑色),为了在addAction:前后设置富文本保持统一,这里先将按钮置为黑色,富文本就会是黑色
  451. [self.actionButton setTitleColor:[SPColorStyle lightBlack_DarkWhiteColor] forState:UIControlStateNormal];
  452. if ([action.attributedTitle.string containsString:@"\n"] || [action.attributedTitle.string containsString:@"\r"]) {
  453. self.actionButton.titleLabel.lineBreakMode = NSLineBreakByWordWrapping;
  454. }
  455. [self.actionButton setAttributedTitle:action.attributedTitle forState:UIControlStateNormal];
  456. // 设置完富文本之后,还原按钮普通文本的颜色,其实这行代码加不加都不影响,只是为了让按钮普通文本的颜色保持跟action.titleColor一致
  457. [self.actionButton setTitleColor:action.titleColor forState:UIControlStateNormal];
  458. } else {
  459. if ([action.title containsString:@"\n"] || [action.title containsString:@"\r"]) {
  460. self.actionButton.titleLabel.lineBreakMode = NSLineBreakByWordWrapping;
  461. }
  462. [self.actionButton setTitle:action.title forState:UIControlStateNormal];
  463. }
  464. [self.actionButton setImage:action.image forState:UIControlStateNormal];
  465. self.actionButton.titleEdgeInsets = UIEdgeInsetsMake(0, action.imageTitleSpacing, 0, -action.imageTitleSpacing);
  466. self.actionButton.imageEdgeInsets = UIEdgeInsetsMake(0, -action.imageTitleSpacing, 0, action.imageTitleSpacing);
  467. }
  468. - (void)addTarget:(id)target action:(SEL)methodAction {
  469. _target = target;
  470. _methodAction = methodAction;
  471. }
  472. - (void)touchUpInside:(UIButton *)sender {
  473. // 用函数指针实现_target调用_methodAction,相当于[_target performSelector:_methodAction withObject:self];但是后者会报警告
  474. SEL selector = _methodAction;
  475. IMP imp = [_target methodForSelector:selector];
  476. void (*func)(id, SEL,SPAlertControllerActionView *) = (void *)imp;
  477. func(_target, selector, self);
  478. }
  479. - (void)touchDown:(UIButton *)sender {
  480. sender.backgroundColor = [SPColorStyle selectedColor];
  481. }
  482. - (void)touchDragExit:(UIButton *)sender {
  483. sender.backgroundColor = [SPColorStyle normalColor];
  484. }
  485. - (SPAlertController *)findAlertController {
  486. UIResponder *next = [self nextResponder];
  487. do {
  488. if ([next isKindOfClass:[SPAlertController class]]) {
  489. return (SPAlertController *)next;
  490. } else {
  491. next = [next nextResponder];
  492. }
  493. } while (next != nil);
  494. return nil;
  495. }
  496. // 安全区域发生了改变,在这个方法里自动适配iPhoneX
  497. - (void)safeAreaInsetsDidChange {
  498. [super safeAreaInsetsDidChange];
  499. // safeAreaInsets+titleEdgeInsets
  500. self.actionButton.contentEdgeInsets = UIEdgeInsetsAddEdgeInsets(self.safeAreaInsets, _action.titleEdgeInsets);
  501. [self setNeedsUpdateConstraints];
  502. }
  503. UIEdgeInsets UIEdgeInsetsAddEdgeInsets(UIEdgeInsets i1,UIEdgeInsets i2) {
  504. return UIEdgeInsetsMake(i1.top+i2.top, i1.left+i2.left, i1.bottom+i2.bottom, i1.right+i2.right);
  505. }
  506. - (void)updateConstraints {
  507. [super updateConstraints];
  508. UIButton *actionButton = self.actionButton;
  509. if (self.actionButtonConstraints) {
  510. [NSLayoutConstraint deactivateConstraints:self.actionButtonConstraints];
  511. self.actionButtonConstraints = nil;
  512. }
  513. NSMutableArray *actionButtonConstraints = [NSMutableArray array];
  514. [actionButtonConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[actionButton]-0-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(actionButton)]];
  515. [actionButtonConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[actionButton]-0-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(actionButton)]];
  516. // 按钮必须确认高度,因为其父视图及父视图的父视图乃至根视图都没有设置高度,而且必须用NSLayoutRelationEqual,如果用NSLayoutRelationGreaterThanOrEqual,虽然也能撑起父视图,但是当某个按钮的高度有所变化以后,stackView会将其余按钮按的高度同比增减。
  517. // titleLabel的内容自适应的高度
  518. CGFloat labelH = actionButton.titleLabel.intrinsicContentSize.height;
  519. // 按钮的上下内边距之和
  520. CGFloat topBottom_insetsSum = actionButton.contentEdgeInsets.top+actionButton.contentEdgeInsets.bottom;
  521. // 文字的上下间距之和,等于SP_ACTION_HEIGHT-默认字体大小,这是为了保证文字上下有一个固定间距值,不至于使文字靠按钮太紧,,由于按钮内容默认垂直居中,所以最终的顶部或底部间距为topBottom_marginSum/2.0,这个间距,几乎等于18号字体时,最小高度为49时的上下间距
  522. CGFloat topBottom_marginSum = SP_ACTION_HEIGHT-[UIFont systemFontOfSize:SP_ACTION_TITLE_FONTSIZE].lineHeight;
  523. // 按钮高度
  524. CGFloat buttonH = labelH+topBottom_insetsSum+topBottom_marginSum;
  525. UIStackView *stackView = (UIStackView *)self.superview;
  526. NSLayoutRelation relation = NSLayoutRelationEqual;
  527. if ([stackView isKindOfClass:[UIStackView class]] && stackView.axis == UILayoutConstraintAxisHorizontal) {
  528. relation = NSLayoutRelationGreaterThanOrEqual;
  529. }
  530. // 如果字体保持默认18号,只有一行文字时最终结果约等于SP_ACTION_HEIGHT
  531. NSLayoutConstraint *buttonHonstraint = [NSLayoutConstraint constraintWithItem:actionButton attribute:NSLayoutAttributeHeight relatedBy:relation toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:buttonH];
  532. buttonHonstraint.priority = 999;
  533. [actionButtonConstraints addObject:buttonHonstraint];
  534. // 给一个最小高度,当按钮字体很小时,如果还按照上面的高度计算,高度会比较小
  535. NSLayoutConstraint *minHConstraint = [NSLayoutConstraint constraintWithItem:actionButton attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:SP_ACTION_HEIGHT+topBottom_insetsSum];
  536. minHConstraint.priority = UILayoutPriorityRequired;
  537. [self addConstraints:actionButtonConstraints];
  538. self.actionButtonConstraints = actionButtonConstraints;
  539. }
  540. - (UIButton *)actionButton {
  541. if (!_actionButton) {
  542. UIButton *actionButton = [UIButton buttonWithType:UIButtonTypeCustom];
  543. actionButton.backgroundColor = [SPColorStyle normalColor];
  544. actionButton.translatesAutoresizingMaskIntoConstraints = NO;
  545. actionButton.titleLabel.textAlignment = NSTextAlignmentCenter;
  546. actionButton.titleLabel.adjustsFontSizeToFitWidth = YES;
  547. actionButton.titleLabel.baselineAdjustment = UIBaselineAdjustmentAlignCenters;
  548. actionButton.titleLabel.minimumScaleFactor = 0.5;
  549. [actionButton addTarget:self action:@selector(touchUpInside:) forControlEvents:UIControlEventTouchUpInside]; // 手指按下然后在按钮有效事件范围内抬起
  550. [actionButton addTarget:self action:@selector(touchDown:) forControlEvents:UIControlEventTouchDown | UIControlEventTouchDragInside]; // 手指按下或者手指按下后往外拽再往内拽
  551. [actionButton addTarget:self action:@selector(touchDragExit:) forControlEvents:UIControlEventTouchDragExit | UIControlEventTouchUpOutside | UIControlEventTouchCancel]; // 手指被迫停止、手指按下后往外拽或者取消,取消的可能性:比如点击的那一刻突然来电话
  552. [self addSubview:actionButton];
  553. _actionButton = actionButton;
  554. }
  555. return _actionButton;
  556. }
  557. @end
  558. #pragma mark ---------------------------- SPAlertControllerActionView end --------------------------------
  559. #pragma mark ---------------------------- SPInterfaceActionSequenceView begin --------------------------------
  560. @interface SPInterfaceActionSequenceView : UIView
  561. @property (nonatomic, weak) UIScrollView *scrollView;
  562. @property (nonatomic, weak) UIView *contentView;
  563. @property (nonatomic, weak) UIView *cancelView;
  564. @property (nonatomic, weak) SPInterfaceActionItemSeparatorView *cancelActionLine;
  565. @property (nonatomic, weak) UIStackView *stackView;
  566. @property (nonatomic, strong) SPAlertAction *cancelAction;
  567. @property (nonatomic, strong) NSMutableArray *actionLineConstraints;
  568. @property (nonatomic, strong) NSMutableArray *actions;
  569. @property (nonatomic, assign) UIStackViewDistribution stackViewDistribution;
  570. @property (nonatomic, assign) UILayoutConstraintAxis axis;
  571. @property (nonatomic, copy) void (^buttonClickedInActionViewBlock)(NSInteger index);
  572. @end
  573. @implementation SPInterfaceActionSequenceView
  574. - (void)setAxis:(UILayoutConstraintAxis)axis {
  575. _axis = axis;
  576. self.stackView.axis = axis;
  577. [self setNeedsUpdateConstraints];
  578. }
  579. - (void)setStackViewDistribution:(UIStackViewDistribution)stackViewDistribution {
  580. _stackViewDistribution = stackViewDistribution;
  581. self.stackView.distribution = stackViewDistribution;
  582. [self setNeedsUpdateConstraints];
  583. }
  584. - (void)buttonClickedInActionView:(SPAlertControllerActionView *)actionView {
  585. NSInteger index = [self.actions indexOfObject:actionView.action];
  586. if (self.buttonClickedInActionViewBlock) {
  587. self.buttonClickedInActionViewBlock(index);
  588. }
  589. }
  590. - (void)setCustomSpacing:(CGFloat)spacing afterActionIndex:(NSInteger)index {
  591. UIStackView *stackView = self.stackView;
  592. SPAlertControllerActionView *actionView = stackView.arrangedSubviews[index];
  593. actionView.afterSpacing = spacing;
  594. if (@available(iOS 11.0, *)) {
  595. [self.stackView setCustomSpacing:spacing afterView:actionView];
  596. }
  597. [self updateLineConstraints];
  598. }
  599. - (CGFloat)customSpacingAfterActionIndex:(NSInteger)index {
  600. UIStackView *stackView = self.stackView;
  601. SPAlertControllerActionView *actionView = stackView.arrangedSubviews[index];
  602. if (@available(iOS 11.0, *)) {
  603. return [self.stackView customSpacingAfterView:actionView];
  604. } else {
  605. return 0.0;
  606. }
  607. }
  608. - (void)addAction:(SPAlertAction *)action {
  609. [self.actions addObject:action];
  610. UIStackView *stackView = self.stackView;
  611. SPAlertControllerActionView *currentActionView = [[SPAlertControllerActionView alloc] init];
  612. currentActionView.action = action;
  613. [currentActionView addTarget:self action:@selector(buttonClickedInActionView:)];
  614. [stackView addArrangedSubview:currentActionView];
  615. if (stackView.arrangedSubviews.count > 1) { // arrangedSubviews个数大于1,说明本次添加至少是第2次添加,此时要加一条分割线
  616. [self addLineForStackView:stackView];
  617. }
  618. [self setNeedsUpdateConstraints];
  619. }
  620. - (void)addCancelAction:(SPAlertAction *)action {
  621. // 如果已经存在取消样式的按钮,则直接崩溃
  622. NSAssert(!_cancelAction, @"SPAlertController can only have one action with a style of SPAlertActionStyleCancel");
  623. _cancelAction = action;
  624. [self.actions addObject:action];
  625. SPAlertControllerActionView *cancelActionView = [[SPAlertControllerActionView alloc] init];
  626. cancelActionView.translatesAutoresizingMaskIntoConstraints = NO;
  627. cancelActionView.action = action;
  628. [cancelActionView addTarget:self action:@selector(buttonClickedInActionView:)];
  629. [self.cancelView addSubview:cancelActionView];
  630. [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[cancelActionView]-0-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(cancelActionView)]];
  631. [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[cancelActionView]-0-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(cancelActionView)]];
  632. [self setNeedsUpdateConstraints];
  633. }
  634. // 为stackView添加分割线(细节)
  635. - (void)addLineForStackView:(UIStackView *)stackView {
  636. SPInterfaceActionItemSeparatorView *actionLine = [[SPInterfaceActionItemSeparatorView alloc] init];
  637. actionLine.translatesAutoresizingMaskIntoConstraints = NO;
  638. // 这里必须用addSubview:,不能用addArrangedSubview:,因为分割线不参与排列布局
  639. [stackView addSubview:actionLine];
  640. }
  641. // 从一个数组筛选出不在另一个数组中的数组
  642. - (NSArray *)filteredArrayFromArray:(NSArray *)array notInArray:(NSArray *)otherArray {
  643. NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NOT (SELF in %@)", otherArray];
  644. // 用谓词语句筛选出所有的分割线
  645. NSArray *subArray = [array filteredArrayUsingPredicate:predicate];
  646. return subArray;
  647. }
  648. // 更新分割线约束(细节)
  649. - (void)updateLineConstraints {
  650. UIStackView *stackView = self.stackView;
  651. NSArray *arrangedSubviews = stackView.arrangedSubviews;
  652. if (arrangedSubviews.count <= 1) return;
  653. // 用谓词语句筛选出所有的分割线
  654. NSArray *lines = [self filteredArrayFromArray:stackView.subviews notInArray:stackView.arrangedSubviews];
  655. if (arrangedSubviews.count < lines.count) return;
  656. NSMutableArray *actionLineConstraints = [NSMutableArray array];
  657. if (self.actionLineConstraints) {
  658. [NSLayoutConstraint deactivateConstraints:self.actionLineConstraints];
  659. self.actionLineConstraints = nil;
  660. }
  661. for (int i = 0; i < lines.count; i++) {
  662. SPInterfaceActionItemSeparatorView *actionLine = lines[i];
  663. SPAlertControllerActionView *actionView1 = arrangedSubviews[i];
  664. SPAlertControllerActionView *actionView2 = arrangedSubviews[i+1];
  665. if (self.axis == UILayoutConstraintAxisHorizontal) {
  666. [actionLineConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[actionLine]-0-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(actionLine)]];
  667. [actionLineConstraints addObject:[NSLayoutConstraint constraintWithItem:actionLine attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:actionView1 attribute:NSLayoutAttributeRight multiplier:1.0 constant:0]];
  668. [actionLineConstraints addObject:[NSLayoutConstraint constraintWithItem:actionLine attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:actionView2 attribute:NSLayoutAttributeLeft multiplier:1.0 constant:0]];
  669. [actionLineConstraints addObject:[NSLayoutConstraint constraintWithItem:actionLine attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:actionView1.afterSpacing]];
  670. } else {
  671. [actionLineConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[actionLine]-0-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(actionLine)]];
  672. [actionLineConstraints addObject:[NSLayoutConstraint constraintWithItem:actionLine attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:actionView1 attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0]];
  673. [actionLineConstraints addObject:[NSLayoutConstraint constraintWithItem:actionLine attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:actionView2 attribute:NSLayoutAttributeTop multiplier:1.0 constant:0]];
  674. [actionLineConstraints addObject:[NSLayoutConstraint constraintWithItem:actionLine attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:actionView1.afterSpacing]];
  675. }
  676. }
  677. [NSLayoutConstraint activateConstraints:actionLineConstraints];
  678. self.actionLineConstraints = actionLineConstraints;
  679. }
  680. - (void)updateConstraints {
  681. [super updateConstraints];
  682. UIView *scrollView = self.scrollView;
  683. UIView *contentView = self.contentView;
  684. UIView *cancelView = self.cancelView;
  685. SPInterfaceActionItemSeparatorView *cancelActionLine = self.cancelActionLine;
  686. [NSLayoutConstraint deactivateConstraints:self.constraints];
  687. if (scrollView && scrollView.superview) {
  688. // 对scrollView布局
  689. NSMutableArray *scrollViewConstraints = [NSMutableArray array];
  690. [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[scrollView]-0-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(scrollView)]];
  691. [scrollViewConstraints addObject:[NSLayoutConstraint constraintWithItem:scrollView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeTop multiplier:1.0 constant:0]];
  692. if (cancelActionLine.superview) {
  693. [scrollViewConstraints addObject:[NSLayoutConstraint constraintWithItem:scrollView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:cancelActionLine attribute:NSLayoutAttributeTop multiplier:1.0 constant:0]];
  694. } else {
  695. [scrollViewConstraints addObject:[NSLayoutConstraint constraintWithItem:scrollView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0]];
  696. }
  697. [NSLayoutConstraint activateConstraints:scrollViewConstraints];
  698. [NSLayoutConstraint deactivateConstraints:scrollView.constraints];
  699. // 对contentView布局
  700. [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[contentView]-0-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(contentView)]];
  701. [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[contentView]-0-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(contentView)]];
  702. [[NSLayoutConstraint constraintWithItem:contentView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:scrollView attribute:NSLayoutAttributeWidth multiplier:1.0 constant:0] setActive:YES];
  703. NSLayoutConstraint *equalHeightConstraint = [NSLayoutConstraint constraintWithItem:contentView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:scrollView attribute:NSLayoutAttributeHeight multiplier:1.0 constant:0];
  704. // 计算scrolView的最小和最大高度,下面这个if语句是保证当actions的g总个数大于4时,scrollView的高度至少为4个半SP_ACTION_HEIGHT的高度,否则自适应内容
  705. CGFloat minHeight = 0.0;
  706. if (_axis == UILayoutConstraintAxisVertical) {
  707. if (self.cancelAction) {
  708. if (self.actions.count > 4) { // 如果有取消按钮且action总个数大于4,则除去取消按钮之外的其余部分的高度至少为3个半SP_ACTION_HEIGHT的高度,即加上取消按钮就是总高度至少为4个半SP_ACTION_HEIGHT的高度
  709. minHeight = SP_ACTION_HEIGHT * 3.5;
  710. equalHeightConstraint.priority = 997.0f; // 优先级为997,必须小于998.0,因为头部如果内容过多时高度也会有限制,头部的优先级为998.0.这里定的规则是,当头部和action部分同时过多时,头部的优先级更高,但是它不能高到以至于action部分小于最小高度
  711. } else { // 如果有取消按钮但action的个数大不于4,则该多高就显示多高
  712. equalHeightConstraint.priority = 1000.0f; // 由子控件撑起
  713. }
  714. } else {
  715. if (self.actions.count > 4) {
  716. minHeight = SP_ACTION_HEIGHT * 4.5;
  717. equalHeightConstraint.priority = 997.0f;
  718. } else {
  719. equalHeightConstraint.priority = 1000.0f;
  720. }
  721. }
  722. } else {
  723. minHeight = SP_ACTION_HEIGHT;
  724. }
  725. NSLayoutConstraint *minHeightConstraint = [NSLayoutConstraint constraintWithItem:scrollView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:minHeight];
  726. minHeightConstraint.priority = 999.0;// 优先级不能大于对话框的最小顶部间距的优先级(999.0)
  727. minHeightConstraint.active = YES;
  728. equalHeightConstraint.active = YES;
  729. UIStackView *stackView = self.stackView;
  730. [NSLayoutConstraint deactivateConstraints:contentView.constraints];
  731. // 对stackView布局
  732. [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[stackView]-0-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(stackView)]];
  733. [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[stackView]-0-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(stackView)]];
  734. // 对stackView里面的分割线布局
  735. [self updateLineConstraints];
  736. }
  737. if (self.cancelActionLine.superview) { // cancelActionLine有superView则必有scrollView和cancelView
  738. NSMutableArray *cancelActionLineConstraints = [NSMutableArray array];
  739. [cancelActionLineConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[cancelActionLine]-0-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(cancelActionLine)]];
  740. [cancelActionLineConstraints addObject:[NSLayoutConstraint constraintWithItem:cancelActionLine attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:cancelView attribute:NSLayoutAttributeTop multiplier:1.0f constant:0]];
  741. [cancelActionLineConstraints addObject:[NSLayoutConstraint constraintWithItem:cancelActionLine attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0f constant:8.0]];
  742. [NSLayoutConstraint activateConstraints:cancelActionLineConstraints];
  743. }
  744. // 对cancelView布局
  745. if (self.cancelAction) { // 有取消样式的按钮才对cancelView布局
  746. NSMutableArray *cancelViewConstraints = [NSMutableArray array];
  747. [cancelViewConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[cancelView]-0-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(cancelView)]];
  748. [cancelViewConstraints addObject:[NSLayoutConstraint constraintWithItem:cancelView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0]];
  749. if (!self.cancelActionLine.superview) {
  750. [cancelViewConstraints addObject:[NSLayoutConstraint constraintWithItem:cancelView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeTop multiplier:1.0 constant:0]];
  751. }
  752. [NSLayoutConstraint activateConstraints:cancelViewConstraints];
  753. }
  754. }
  755. - (UIScrollView *)scrollView {
  756. if (!_scrollView) {
  757. UIScrollView *scrollView = [[UIScrollView alloc] init];
  758. scrollView.showsHorizontalScrollIndicator = NO;
  759. scrollView.translatesAutoresizingMaskIntoConstraints = NO;
  760. if (@available(iOS 11.0, *)) {
  761. scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
  762. }
  763. if ((self.cancelAction && self.actions.count > 1) || (!self.cancelAction && self.actions.count > 0)) {
  764. [self addSubview:scrollView];
  765. }
  766. _scrollView = scrollView;
  767. }
  768. return _scrollView;
  769. }
  770. - (UIView *)contentView {
  771. if (!_contentView) {
  772. UIView *contentView = [[UIView alloc] init];
  773. contentView.translatesAutoresizingMaskIntoConstraints = NO;
  774. [self.scrollView addSubview:contentView];
  775. _contentView = contentView;
  776. }
  777. return _contentView;
  778. }
  779. - (UIStackView *)stackView {
  780. if (!_stackView) {
  781. UIStackView *stackView = [[UIStackView alloc] init];
  782. stackView.translatesAutoresizingMaskIntoConstraints = NO;
  783. stackView.distribution = UIStackViewDistributionFillProportionally;
  784. stackView.spacing = SP_LINE_WIDTH; // 该间距腾出来的空间显示分割线
  785. stackView.axis = UILayoutConstraintAxisVertical;
  786. [self.contentView addSubview:stackView];
  787. _stackView = stackView;
  788. }
  789. return _stackView;
  790. }
  791. - (UIView *)cancelView {
  792. if (!_cancelView) {
  793. UIView *cancelView = [[UIView alloc] init];
  794. cancelView.translatesAutoresizingMaskIntoConstraints = NO;
  795. if (self.cancelAction) {
  796. [self addSubview:cancelView];
  797. }
  798. _cancelView = cancelView;
  799. }
  800. return _cancelView;
  801. }
  802. - (SPInterfaceActionItemSeparatorView *)cancelActionLine {
  803. if (!_cancelActionLine) {
  804. SPInterfaceActionItemSeparatorView *cancelActionLine = [[SPInterfaceActionItemSeparatorView alloc] init];
  805. cancelActionLine.translatesAutoresizingMaskIntoConstraints = NO;
  806. if (self.cancelView.superview && self.scrollView.superview) {
  807. [self addSubview:cancelActionLine];
  808. }
  809. _cancelActionLine = cancelActionLine;
  810. }
  811. return _cancelActionLine;
  812. }
  813. - (NSMutableArray *)actions {
  814. if (!_actions) {
  815. _actions = [[NSMutableArray alloc] init];
  816. }
  817. return _actions;
  818. }
  819. @end
  820. #pragma mark ---------------------------- SPInterfaceActionSequenceView end --------------------------------
  821. #pragma mark ---------------------------- SPAlertController begin --------------------------------
  822. @interface SPAlertController () <UIViewControllerTransitioningDelegate>
  823. @property (nonatomic, strong) UIView *alertControllerView;
  824. @property (nonatomic, weak) UIView *containerView;
  825. @property (nonatomic, weak) UIView *alertView;
  826. @property (nonatomic, strong) UIView *customAlertView;
  827. @property (nonatomic, weak) SPInterfaceHeaderScrollView *headerView;
  828. @property (nonatomic, strong) UIView *customHeaderView;
  829. @property (nonatomic, weak) SPInterfaceActionSequenceView *actionSequenceView;
  830. @property (nonatomic, strong) UIView *customActionSequenceView;
  831. @property (nonatomic, strong) UIView *componentView;
  832. @property (nonatomic, assign) CGSize customViewSize;
  833. @property (nonatomic, weak) SPInterfaceActionItemSeparatorView *headerActionLine;
  834. @property (nonatomic, strong) NSMutableArray *headerActionLineConstraints;
  835. @property (nonatomic, weak) SPInterfaceActionItemSeparatorView *componentActionLine;
  836. @property (nonatomic, strong) NSMutableArray *componentViewConstraints;
  837. @property (nonatomic, strong) NSMutableArray *componentActionLineConstraints;
  838. @property (nonatomic, strong) UIView *dimmingKnockoutBackdropView;
  839. @property (nonatomic, strong) NSMutableArray *alertControllerViewConstraints;
  840. @property (nonatomic, strong) NSMutableArray *headerViewConstraints;
  841. @property (nonatomic, strong) NSMutableArray *actionSequenceViewConstraints;
  842. @property (nonatomic, assign) SPAlertControllerStyle preferredStyle;
  843. @property (nonatomic, assign) SPAlertAnimationType animationType;
  844. @property (nonatomic, assign) UIBlurEffectStyle backgroundViewAppearanceStyle;
  845. @property (nonatomic, assign) CGFloat backgroundViewAlpha;
  846. // action数组
  847. @property (nonatomic) NSArray<SPAlertAction *> *actions;
  848. // textFiled数组
  849. @property (nonatomic) NSArray<UITextField *> *textFields;
  850. // 除去取消样式action的其余action数组
  851. @property (nonatomic) NSMutableArray<SPAlertAction *> *otherActions;
  852. @property (nonatomic, assign) BOOL isForceLayout; // 是否强制排列,外界设置了actionAxis属性认为是强制
  853. @property (nonatomic, assign) BOOL isForceOffset; // 是否强制偏移,外界设置了offsetForAlert属性认为是强制
  854. @end
  855. @implementation SPAlertController
  856. @synthesize title = _title;
  857. #pragma mark - public
  858. + (instancetype)alertControllerWithTitle:(NSString *)title message:(NSString *)message preferredStyle:(SPAlertControllerStyle)preferredStyle {
  859. SPAlertController *alertVc = [[SPAlertController alloc] initWithTitle:title message:message customAlertView:nil customHeaderView:nil customActionSequenceView:nil componentView:nil preferredStyle:preferredStyle animationType:SPAlertAnimationTypeDefault];
  860. return alertVc;
  861. }
  862. + (instancetype)alertControllerWithTitle:(NSString *)title message:(NSString *)message preferredStyle:(SPAlertControllerStyle)preferredStyle animationType:(SPAlertAnimationType)animationType {
  863. SPAlertController *alertVc = [[SPAlertController alloc] initWithTitle:title message:message customAlertView:nil customHeaderView:nil customActionSequenceView:nil componentView:nil preferredStyle:preferredStyle animationType:animationType];
  864. return alertVc;
  865. }
  866. + (instancetype)alertControllerWithCustomAlertView:(UIView *)customAlertView preferredStyle:(SPAlertControllerStyle)preferredStyle animationType:(SPAlertAnimationType)animationType {
  867. SPAlertController *alertVc = [[SPAlertController alloc] initWithTitle:nil message:nil customAlertView:customAlertView customHeaderView:nil customActionSequenceView:nil componentView:nil preferredStyle:preferredStyle animationType:animationType];
  868. return alertVc;
  869. }
  870. + (instancetype)alertControllerWithCustomHeaderView:(UIView *)customHeaderView preferredStyle:(SPAlertControllerStyle)preferredStyle animationType:(SPAlertAnimationType)animationType {
  871. SPAlertController *alertVc = [[SPAlertController alloc] initWithTitle:nil message:nil customAlertView:nil customHeaderView:customHeaderView customActionSequenceView:nil componentView:nil preferredStyle:preferredStyle animationType:animationType];
  872. return alertVc;
  873. }
  874. + (instancetype)alertControllerWithCustomActionSequenceView:(UIView *)customActionSequenceView title:(nullable NSString *)title message:(nullable NSString *)message preferredStyle:(SPAlertControllerStyle)preferredStyle animationType:(SPAlertAnimationType)animationType {
  875. SPAlertController *alertVc = [[SPAlertController alloc] initWithTitle:title message:message customAlertView:nil customHeaderView:nil customActionSequenceView:customActionSequenceView componentView:nil preferredStyle:preferredStyle animationType:animationType];
  876. return alertVc;
  877. }
  878. - (void)setOffsetForAlert:(CGPoint)offsetForAlert animated:(BOOL)animated {
  879. _offsetForAlert = offsetForAlert;
  880. _isForceOffset = YES;
  881. [self makeViewOffsetWithAnimated:animated];
  882. }
  883. - (void)insertComponentView:(UIView *)componentView {
  884. _componentView = componentView;
  885. }
  886. // 添加action
  887. - (void)addAction:(SPAlertAction *)action {
  888. NSMutableArray *actions = self.actions.mutableCopy;
  889. [actions addObject:action];
  890. self.actions = actions;
  891. if (self.preferredStyle == SPAlertControllerStyleAlert) { // alert样式不论是否为取消样式的按钮,都直接按顺序添加
  892. if (action.style != SPAlertActionStyleCancel) {
  893. [self.otherActions addObject:action];
  894. }
  895. [self.actionSequenceView addAction:action];
  896. } else { // actionSheet样式
  897. if (action.style == SPAlertActionStyleCancel) { // 如果是取消样式的按钮
  898. [self.actionSequenceView addCancelAction:action];
  899. } else {
  900. [self.otherActions addObject:action];
  901. [self.actionSequenceView addAction:action];
  902. }
  903. }
  904. if (!self.isForceLayout) { // 如果为NO,说明外界没有设置actionAxis,此时按照默认方式排列
  905. if (self.preferredStyle == SPAlertControllerStyleAlert) {
  906. if (self.actions.count > _maxNumberOfActionHorizontalArrangementForAlert) { // alert样式下,action的个数大于2时垂直排列,这里不等式右边写_maxNumberOfActionHorizontalArrangementForAlert是为了让被废弃的_maxNumberOfActionHorizontalArrangementForAlert依然生效
  907. _actionAxis = UILayoutConstraintAxisVertical; // 本框架任何一处都不允许调用actionAxis的setter方法,如果调用了则无法判断是外界调用还是内部调用
  908. [self updateActionAxis];
  909. } else { // action的个数小于等于2,action水平排列
  910. _actionAxis = UILayoutConstraintAxisHorizontal;
  911. [self updateActionAxis];
  912. }
  913. } else { // actionSheet样式下默认垂直排列
  914. _actionAxis = UILayoutConstraintAxisVertical;
  915. [self updateActionAxis];
  916. }
  917. } else {
  918. [self updateActionAxis];
  919. }
  920. // 这个block是保证外界在添加action之后再设置action属性时依然生效;当使用时在addAction之后再设置action的属性时,会回调这个block
  921. __weak typeof(self) weakSelf = self;
  922. action.propertyChangedBlock = ^(SPAlertAction *action, BOOL needUpdateConstraints) {
  923. __strong typeof(self) strongSelf = weakSelf;
  924. if (strongSelf.preferredStyle == SPAlertControllerStyleAlert) {
  925. // alert样式下:arrangedSubviews数组和actions是对应的
  926. NSInteger index = [strongSelf.actions indexOfObject:action];
  927. SPAlertControllerActionView *actionView = [strongSelf.actionSequenceView.stackView.arrangedSubviews objectAtIndex:index];
  928. if ([actionView isKindOfClass:[SPAlertControllerActionView class]]) {
  929. actionView.action = action;
  930. }
  931. if (strongSelf.presentationController.presentingViewController) {
  932. // 文字显示不全处理
  933. [strongSelf handleIncompleteTextDisplay];
  934. }
  935. } else {
  936. if (action.style == SPAlertActionStyleCancel) {
  937. // cancelView中只有唯一的一个actionView
  938. SPAlertControllerActionView *actionView = [strongSelf.actionSequenceView.cancelView.subviews lastObject];
  939. if ([actionView isKindOfClass:[SPAlertControllerActionView class]]) { // 这个判断可以不加,加判断是防止有一天改动框架不小心在cancelView中加了新的view产生安全隐患
  940. actionView.action = action;
  941. }
  942. } else {
  943. // actionSheet样式下:arrangedSubviews数组和otherActions是对应的
  944. NSInteger index = [strongSelf.otherActions indexOfObject:action];
  945. SPAlertControllerActionView *actionView = [strongSelf.actionSequenceView.stackView.arrangedSubviews objectAtIndex:index];
  946. if ([actionView isKindOfClass:[SPAlertControllerActionView class]]) {
  947. actionView.action = action;
  948. }
  949. }
  950. }
  951. if (strongSelf.presentationController.presentingViewController && needUpdateConstraints) { // 如果在present完成后的某个时刻再去设置action的属性,字体等改变需要更新布局
  952. [strongSelf.actionSequenceView setNeedsUpdateConstraints];
  953. }
  954. };
  955. }
  956. // 添加文本输入框
  957. - (void)addTextFieldWithConfigurationHandler:(void (^)(UITextField * _Nonnull))configurationHandler {
  958. NSAssert(self.preferredStyle == SPAlertControllerStyleAlert,@"SPAlertController does not allow 'addTextFieldWithConfigurationHandler:' to be called in the style of SPAlertControllerStyleActionSheet");
  959. UITextField *textField = [[UITextField alloc] init];
  960. textField.translatesAutoresizingMaskIntoConstraints = NO;
  961. textField.backgroundColor = [SPColorStyle textViewBackgroundColor];
  962. // 系统的UITextBorderStyleLine样式线条过于黑,所以自己设置
  963. textField.layer.borderWidth = SP_LINE_WIDTH;
  964. // 这里设置的颜色是静态的,动态设置CGColor,还需要监听深浅模式的切换
  965. textField.layer.borderColor = [SPColorStyle colorPairsWithStaticLightColor:[SPColorStyle lineColor] darkColor:[SPColorStyle darkLineColor]].CGColor;
  966. // 在左边设置一张view,充当光标左边的间距,否则光标紧贴textField不美观
  967. textField.leftView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 5, 0)];
  968. textField.leftView.userInteractionEnabled = NO;
  969. textField.leftViewMode = UITextFieldViewModeAlways;
  970. textField.font = [UIFont systemFontOfSize:14];
  971. // 去掉textField键盘上部的联想条
  972. textField.autocorrectionType = UITextAutocorrectionTypeNo;
  973. [textField addTarget:self action:@selector(textFieldDidEndOnExit:) forControlEvents:UIControlEventEditingDidEndOnExit];
  974. NSMutableArray *array = self.textFields.mutableCopy;
  975. [array addObject:textField];
  976. self.textFields = array;
  977. [self.headerView addTextField:textField];
  978. if (configurationHandler) {
  979. configurationHandler(textField);
  980. }
  981. }
  982. - (void)setCustomSpacing:(CGFloat)spacing afterAction:(SPAlertAction *)action {
  983. if (@available(iOS 11.0, *)) {
  984. if (action == nil) return;
  985. if (action.style == SPAlertActionStyleCancel) {
  986. NSLog(@"*** warning in -[SPAlertController setCustomSpacing:afterAction:]: 'the -action must not be a action with SPAlertActionStyleCancel style'");
  987. } else if (![self.otherActions containsObject:action]) {
  988. NSLog(@"*** warning in -[SPAlertController setCustomSpacing:afterAction:]: 'the -action must be contained in the -actions array, not a action with SPAlertActionStyleCancel style'");
  989. } else {
  990. NSInteger index = [self.otherActions indexOfObject:action];
  991. [self.actionSequenceView setCustomSpacing:spacing afterActionIndex:index];
  992. }
  993. } else {
  994. // 报异常
  995. [self doesNotRecognizeSelector:@selector(setCustomSpacing:afterAction:)];
  996. }
  997. }
  998. - (CGFloat)customSpacingAfterAction:(SPAlertAction *)action {
  999. if (@available(iOS 11.0, *)) {
  1000. if ([self.otherActions containsObject:action]) {
  1001. NSInteger index = [self.otherActions indexOfObject:action];
  1002. return [self.actionSequenceView customSpacingAfterActionIndex:index];
  1003. }
  1004. } else {
  1005. // 报异常
  1006. [self doesNotRecognizeSelector:@selector(setCustomSpacing:afterAction:)];
  1007. }
  1008. return 0.0;
  1009. }
  1010. - (void)setBackgroundViewAppearanceStyle:(UIBlurEffectStyle)style alpha:(CGFloat)alpha {
  1011. _backgroundViewAppearanceStyle = style;
  1012. _backgroundViewAlpha = alpha;
  1013. }
  1014. - (void)updateCustomViewSize:(CGSize)size {
  1015. _customViewSize = size;
  1016. [self layoutAlertControllerView];
  1017. [self layoutChildViews];
  1018. }
  1019. #pragma mark - Private
  1020. - (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 {
  1021. self = [self init];
  1022. _title = title;
  1023. _message = message;
  1024. _preferredStyle = preferredStyle;
  1025. // 如果是默认动画,preferredStyle为alert时动画默认为alpha,preferredStyle为actionShee时动画默认为fromBottom
  1026. if (animationType == SPAlertAnimationTypeDefault) {
  1027. if (preferredStyle == SPAlertControllerStyleAlert) {
  1028. animationType = SPAlertAnimationTypeShrink;
  1029. } else {
  1030. animationType = SPAlertAnimationTypeFromBottom;
  1031. }
  1032. }
  1033. _animationType = animationType;
  1034. if (preferredStyle == SPAlertControllerStyleAlert) {
  1035. _maxMarginForAlert = (MIN(SP_SCREEN_WIDTH, SP_SCREEN_HEIGHT) - 275) / 2.0;
  1036. _minDistanceToEdges = (MIN(SP_SCREEN_WIDTH, SP_SCREEN_HEIGHT) - 275) / 2.0;
  1037. _cornerRadius = 6.0;
  1038. } else {
  1039. _minDistanceToEdges = 70;
  1040. _maxTopMarginForActionSheet = 70;
  1041. _cornerRadius = 13.0;
  1042. }
  1043. if (preferredStyle == SPAlertControllerStyleAlert) {
  1044. _actionAxis = UILayoutConstraintAxisHorizontal;
  1045. } else {
  1046. _actionAxis = UILayoutConstraintAxisVertical;
  1047. }
  1048. _customAlertView = customAlertView;
  1049. _customHeaderView = customHeaderView;
  1050. _customActionSequenceView = customActionSequenceView;
  1051. _componentView = componentView; // componentView参数是为了支持老版本的自定义footerView
  1052. return self;
  1053. }
  1054. - (instancetype)init {
  1055. if (self = [super init]) {
  1056. [self initialize];
  1057. }
  1058. return self;
  1059. }
  1060. - (void)initialize {
  1061. // 视图控制器定义它呈现视图控制器的过渡风格(默认为NO)
  1062. self.providesPresentationContextTransitionStyle = YES;
  1063. self.definesPresentationContext = YES;
  1064. self.modalPresentationStyle = UIModalPresentationCustom;
  1065. self.transitioningDelegate = self;
  1066. _titleFont = [UIFont boldSystemFontOfSize:18];
  1067. _titleColor = [SPColorStyle lightBlack_DarkWhiteColor];
  1068. _messageFont = [UIFont systemFontOfSize:16];
  1069. _messageColor = [SPColorStyle grayColor];
  1070. _textAlignment = NSTextAlignmentCenter;
  1071. _imageLimitSize = CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX);
  1072. _cornerRadiusForAlert = 6.0;
  1073. _backgroundViewAppearanceStyle = -1;
  1074. _backgroundViewAlpha = 0.5;
  1075. _tapBackgroundViewDismiss = YES;
  1076. _needDialogBlur = NO;
  1077. _maxNumberOfActionHorizontalArrangementForAlert = 2;
  1078. }
  1079. - (void)layoutAlertControllerView {
  1080. if (!self.alertControllerView.superview) return;
  1081. if (self.alertControllerViewConstraints) {
  1082. [NSLayoutConstraint deactivateConstraints:self.alertControllerViewConstraints];
  1083. self.alertControllerViewConstraints = nil;
  1084. }
  1085. if (self.preferredStyle == SPAlertControllerStyleAlert) { // alert样式
  1086. [self layoutAlertControllerViewForAlertStyle];
  1087. } else { // actionSheet样式
  1088. [self layoutAlertControllerViewForActionSheetStyle];
  1089. }
  1090. }
  1091. - (void)layoutAlertControllerViewForAlertStyle {
  1092. UIView *alertControllerView = self.alertControllerView;
  1093. NSMutableArray *alertControllerViewConstraints = [NSMutableArray array];
  1094. CGFloat topValue = _minDistanceToEdges;
  1095. CGFloat bottomValue = _minDistanceToEdges;
  1096. CGFloat maxWidth = MIN(SP_SCREEN_WIDTH, SP_SCREEN_HEIGHT)-_minDistanceToEdges * 2;
  1097. CGFloat maxHeight = SP_SCREEN_HEIGHT-topValue-bottomValue;
  1098. if (!self.customAlertView) {
  1099. // 当屏幕旋转的时候,为了保持alert样式下的宽高不变,因此取MIN(SP_SCREEN_WIDTH, SP_SCREEN_HEIGHT)
  1100. [alertControllerViewConstraints addObject:[NSLayoutConstraint constraintWithItem:alertControllerView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:maxWidth]];
  1101. } else {
  1102. [alertControllerViewConstraints addObject:[NSLayoutConstraint constraintWithItem:alertControllerView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationLessThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:maxWidth]];
  1103. if (_customViewSize.width) { // 如果宽度没有值,则会假定customAlertView水平方向能由子控件撑起
  1104. // 限制最大宽度,且能保证内部约束不报警告
  1105. CGFloat customWidth = MIN(_customViewSize.width, maxWidth);
  1106. [alertControllerViewConstraints addObject:[NSLayoutConstraint constraintWithItem:alertControllerView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:customWidth]];
  1107. }
  1108. if (_customViewSize.height) { // 如果高度没有值,则会假定customAlertView垂直方向能由子控件撑起
  1109. CGFloat customHeight = MIN(_customViewSize.height, maxHeight);
  1110. [alertControllerViewConstraints addObject:[NSLayoutConstraint constraintWithItem:alertControllerView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:customHeight]];
  1111. }
  1112. }
  1113. NSLayoutConstraint *topConstraint = [NSLayoutConstraint constraintWithItem:alertControllerView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:alertControllerView.superview attribute:NSLayoutAttributeTop multiplier:1.0f constant:topValue];
  1114. topConstraint.priority = 999.0;// 这里优先级为999.0是为了小于垂直中心的优先级,如果含有文本输入框,键盘弹出后,特别是旋转到横屏后,对话框的空间比较小,这个时候优先偏移垂直中心,顶部优先级按理说应该会被忽略,但是由于子控件含有scrollView,所以该优先级仍然会被激活,子控件显示不全scrollView可以滑动。如果外界自定义了整个对话框,且自定义的view上含有文本输入框,子控件不含有scrollView,顶部间距会被忽略
  1115. [alertControllerViewConstraints addObject:topConstraint];
  1116. NSLayoutConstraint *bottomConstraint = [NSLayoutConstraint constraintWithItem:alertControllerView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationLessThanOrEqual toItem:alertControllerView.superview attribute:NSLayoutAttributeBottom multiplier:1.0f constant:-bottomValue];
  1117. bottomConstraint.priority = 999.0; // 优先级跟顶部同理
  1118. [alertControllerViewConstraints addObject:bottomConstraint];
  1119. [alertControllerViewConstraints addObject:[NSLayoutConstraint constraintWithItem:alertControllerView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:alertControllerView.superview attribute:NSLayoutAttributeCenterX multiplier:1.0 constant: _offsetForAlert.x]];
  1120. 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];
  1121. [alertControllerViewConstraints addObject:alertControllerViewConstraintCenterY];
  1122. [NSLayoutConstraint activateConstraints:alertControllerViewConstraints];
  1123. self.alertControllerViewConstraints = alertControllerViewConstraints;
  1124. }
  1125. - (void)layoutAlertControllerViewForActionSheetStyle {
  1126. switch (self.animationType) {
  1127. case SPAlertAnimationTypeFromBottom:
  1128. case SPAlertAnimationTypeRaiseUp:
  1129. default:
  1130. [self layoutAlertControllerViewForAnimationTypeWithHV:@"H"
  1131. equalAttribute:NSLayoutAttributeBottom
  1132. notEqualAttribute:NSLayoutAttributeTop
  1133. lessOrGreaterRelation:NSLayoutRelationGreaterThanOrEqual];
  1134. break;
  1135. case SPAlertAnimationTypeFromTop:
  1136. case SPAlertAnimationTypeDropDown:
  1137. [self layoutAlertControllerViewForAnimationTypeWithHV:@"H"
  1138. equalAttribute:NSLayoutAttributeTop
  1139. notEqualAttribute:NSLayoutAttributeBottom
  1140. lessOrGreaterRelation:NSLayoutRelationLessThanOrEqual];
  1141. break;
  1142. case SPAlertAnimationTypeFromLeft:
  1143. [self layoutAlertControllerViewForAnimationTypeWithHV:@"V"
  1144. equalAttribute:NSLayoutAttributeLeft
  1145. notEqualAttribute:NSLayoutAttributeRight
  1146. lessOrGreaterRelation:NSLayoutRelationLessThanOrEqual];
  1147. break;
  1148. case SPAlertAnimationTypeFromRight:
  1149. [self layoutAlertControllerViewForAnimationTypeWithHV:@"V"
  1150. equalAttribute:NSLayoutAttributeRight
  1151. notEqualAttribute:NSLayoutAttributeLeft
  1152. lessOrGreaterRelation:NSLayoutRelationLessThanOrEqual];
  1153. break;
  1154. }
  1155. }
  1156. - (void)layoutAlertControllerViewForAnimationTypeWithHV:(NSString *)hv
  1157. equalAttribute:(NSLayoutAttribute)equalAttribute
  1158. notEqualAttribute:(NSLayoutAttribute)notEqualAttribute
  1159. lessOrGreaterRelation:(NSLayoutRelation)relation {
  1160. UIView *alertControllerView = self.alertControllerView;
  1161. NSMutableArray *alertControllerViewConstraints = [NSMutableArray array];
  1162. if (!self.customAlertView) {
  1163. [alertControllerViewConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:[NSString stringWithFormat:@"%@:|-0-[alertControllerView]-0-|",hv] options:0 metrics:nil views:NSDictionaryOfVariableBindings(alertControllerView)]];
  1164. } else {
  1165. NSLayoutAttribute centerXorY = [hv isEqualToString:@"H"] ? NSLayoutAttributeCenterX : NSLayoutAttributeCenterY;
  1166. [alertControllerViewConstraints addObject:[NSLayoutConstraint constraintWithItem:alertControllerView attribute:centerXorY relatedBy:NSLayoutRelationEqual toItem:alertControllerView.superview attribute:centerXorY multiplier:1.0 constant:0]];
  1167. if (_customViewSize.width) { // 如果宽度没有值,则会假定customAlertViewh水平方向能由子控件撑起
  1168. CGFloat alertControllerViewWidth = 0.0;
  1169. if ([hv isEqualToString:@"H"]) {
  1170. alertControllerViewWidth = MIN(_customViewSize.width, SP_SCREEN_WIDTH);
  1171. } else {
  1172. alertControllerViewWidth = MIN(_customViewSize.width, SP_SCREEN_WIDTH-_minDistanceToEdges);
  1173. }
  1174. [alertControllerViewConstraints addObject:[NSLayoutConstraint constraintWithItem:alertControllerView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:alertControllerViewWidth]];
  1175. }
  1176. if (_customViewSize.height) { // 如果高度没有值,则会假定customAlertViewh垂直方向能由子控件撑起
  1177. CGFloat alertControllerViewHeight = 0.0;
  1178. if ([hv isEqualToString:@"H"]) {
  1179. alertControllerViewHeight = MIN(_customViewSize.height, SP_SCREEN_HEIGHT-_minDistanceToEdges);
  1180. } else {
  1181. alertControllerViewHeight = MIN(_customViewSize.height, SP_SCREEN_HEIGHT);
  1182. }
  1183. [alertControllerViewConstraints addObject:[NSLayoutConstraint constraintWithItem:alertControllerView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:alertControllerViewHeight]];
  1184. }
  1185. }
  1186. [alertControllerViewConstraints addObject:[NSLayoutConstraint constraintWithItem:alertControllerView attribute:equalAttribute relatedBy:NSLayoutRelationEqual toItem:alertControllerView.superview attribute:equalAttribute multiplier:1.0 constant:0]];
  1187. NSLayoutConstraint *someSideConstraint = [NSLayoutConstraint constraintWithItem:alertControllerView attribute:notEqualAttribute relatedBy:relation toItem:alertControllerView.superview attribute:notEqualAttribute multiplier:1.0 constant:_minDistanceToEdges];
  1188. someSideConstraint.priority = 999.0;
  1189. [alertControllerViewConstraints addObject:someSideConstraint];
  1190. [NSLayoutConstraint activateConstraints:alertControllerViewConstraints];
  1191. self.alertControllerViewConstraints = alertControllerViewConstraints;
  1192. }
  1193. - (void)layoutChildViews {
  1194. // 对头部布局
  1195. [self layoutHeaderView];
  1196. // 对头部和action部分之间的分割线布局
  1197. [self layoutHeaderActionLine];
  1198. // 对组件view布局
  1199. [self layoutComponentView];
  1200. // 对组件view与action部分之间的分割线布局
  1201. [self layoutComponentActionLine];
  1202. // 对action部分布局
  1203. [self layoutActionSequenceView];
  1204. }
  1205. // 对头部布局,高度由子控件撑起
  1206. - (void)layoutHeaderView {
  1207. UIView *headerView = self.customHeaderView ? self.customHeaderView : self.headerView;
  1208. if (!headerView.superview) return;
  1209. UIView *alertView = self.alertView;
  1210. NSMutableArray *headerViewConstraints = [NSMutableArray array];
  1211. if (self.headerViewConstraints) {
  1212. [NSLayoutConstraint deactivateConstraints:self.headerViewConstraints];
  1213. self.headerViewConstraints = nil;
  1214. }
  1215. if (!self.customHeaderView) {
  1216. [headerViewConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[headerView]-0-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(headerView)]];
  1217. } else {
  1218. if (_customViewSize.width) {
  1219. CGFloat maxWidth = [self maxWidth];
  1220. CGFloat headerViewWidth = MIN(maxWidth, _customViewSize.width);
  1221. [headerViewConstraints addObject:[NSLayoutConstraint constraintWithItem:headerView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:headerViewWidth]];
  1222. }
  1223. if (_customViewSize.height) {
  1224. NSLayoutConstraint *customHeightConstraint = [NSLayoutConstraint constraintWithItem:headerView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:_customViewSize.height];
  1225. customHeightConstraint.priority = UILayoutPriorityDefaultHigh;
  1226. [headerViewConstraints addObject:customHeightConstraint];
  1227. }
  1228. [headerViewConstraints addObject:[NSLayoutConstraint constraintWithItem:headerView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:alertView attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0]];
  1229. }
  1230. [headerViewConstraints addObject:[NSLayoutConstraint constraintWithItem:headerView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:alertView attribute:NSLayoutAttributeTop multiplier:1.0 constant:0]];
  1231. if (!self.headerActionLine.superview) {
  1232. [headerViewConstraints addObject:[NSLayoutConstraint constraintWithItem:headerView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:alertView attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0]];
  1233. }
  1234. [NSLayoutConstraint activateConstraints:headerViewConstraints];
  1235. self.headerViewConstraints = headerViewConstraints;
  1236. }
  1237. // 对头部和action部分之间的分割线布局
  1238. - (void)layoutHeaderActionLine {
  1239. if (!self.headerActionLine.superview) return;
  1240. UIView *headerActionLine = self.headerActionLine;
  1241. UIView *headerView = self.customHeaderView ? self.customHeaderView : self.headerView;
  1242. UIView *actionSequenceView = self.customActionSequenceView ? self.customActionSequenceView : self.actionSequenceView;
  1243. NSMutableArray *headerActionLineConstraints = [NSMutableArray array];
  1244. if (self.headerActionLineConstraints) {
  1245. [NSLayoutConstraint deactivateConstraints:self.headerActionLineConstraints];
  1246. self.headerActionLineConstraints = nil;
  1247. }
  1248. [headerActionLineConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[headerActionLine]-0-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(headerActionLine)]];
  1249. [headerActionLineConstraints addObject:[NSLayoutConstraint constraintWithItem:headerActionLine attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:headerView attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0]];
  1250. if (!self.componentView.superview) {
  1251. [headerActionLineConstraints addObject:[NSLayoutConstraint constraintWithItem:headerActionLine attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:actionSequenceView attribute:NSLayoutAttributeTop multiplier:1.0 constant:0]];
  1252. }
  1253. [headerActionLineConstraints addObject:[NSLayoutConstraint constraintWithItem:headerActionLine attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:SP_LINE_WIDTH]];
  1254. [NSLayoutConstraint activateConstraints:headerActionLineConstraints];
  1255. self.headerActionLineConstraints = headerActionLineConstraints;
  1256. }
  1257. // 对组件view布局
  1258. - (void)layoutComponentView {
  1259. if (!self.componentView.superview) return;
  1260. UIView *componentView = self.componentView;
  1261. UIView *headerActionLine = self.headerActionLine;
  1262. UIView *componentActionLine = self.componentActionLine;
  1263. NSMutableArray *componentViewConstraints = [NSMutableArray array];
  1264. if (self.componentViewConstraints) {
  1265. [NSLayoutConstraint deactivateConstraints:self.componentViewConstraints];
  1266. self.componentViewConstraints = nil;
  1267. }
  1268. [componentViewConstraints addObject:[NSLayoutConstraint constraintWithItem:componentView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:headerActionLine attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0]];
  1269. [componentViewConstraints addObject:[NSLayoutConstraint constraintWithItem:componentView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:componentActionLine attribute:NSLayoutAttributeTop multiplier:1.0 constant:0]];
  1270. [componentViewConstraints addObject:[NSLayoutConstraint constraintWithItem:componentView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.alertView attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0]];
  1271. if (_customViewSize.height) {
  1272. NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:componentView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:_customViewSize.height];
  1273. heightConstraint.priority = UILayoutPriorityDefaultHigh; // 750
  1274. [componentViewConstraints addObject:heightConstraint];
  1275. }
  1276. if (_customViewSize.width) {
  1277. CGFloat maxWidth = [self maxWidth];
  1278. CGFloat componentViewWidth = MIN(maxWidth, _customViewSize.width);
  1279. [componentViewConstraints addObject:[NSLayoutConstraint constraintWithItem:componentView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:componentViewWidth]];
  1280. }
  1281. [NSLayoutConstraint activateConstraints:componentViewConstraints];
  1282. self.componentViewConstraints = componentViewConstraints;
  1283. }
  1284. // 对组件view和action部分之间的分割线布局
  1285. - (void)layoutComponentActionLine {
  1286. if (!self.componentActionLine.superview) return;
  1287. UIView *componentActionLine = self.componentActionLine;
  1288. NSMutableArray *componentActionLineConstraints = [NSMutableArray array];
  1289. if (self.componentActionLineConstraints) {
  1290. [NSLayoutConstraint deactivateConstraints:self.componentActionLineConstraints];
  1291. self.componentActionLineConstraints = nil;
  1292. }
  1293. [componentActionLineConstraints addObject:[NSLayoutConstraint constraintWithItem:componentActionLine attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.actionSequenceView attribute:NSLayoutAttributeTop multiplier:1.0 constant:0]];
  1294. [componentActionLineConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[componentActionLine]-0-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(componentActionLine)]];
  1295. [componentActionLineConstraints addObject:[NSLayoutConstraint constraintWithItem:componentActionLine attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:SP_LINE_WIDTH]];
  1296. [NSLayoutConstraint activateConstraints:componentActionLineConstraints];
  1297. self.componentActionLineConstraints = componentActionLineConstraints;
  1298. }
  1299. // 对action部分布局,高度由子控件撑起
  1300. - (void)layoutActionSequenceView {
  1301. UIView *actionSequenceView = self.customActionSequenceView ? self.customActionSequenceView : self.actionSequenceView;
  1302. if (!actionSequenceView.superview) return;
  1303. UIView *alertView = self.alertView;
  1304. UIView *headerActionLine = self.headerActionLine;
  1305. NSMutableArray *actionSequenceViewConstraints = [NSMutableArray array];
  1306. if (self.actionSequenceViewConstraints) {
  1307. [NSLayoutConstraint deactivateConstraints:self.actionSequenceViewConstraints];
  1308. self.actionSequenceViewConstraints = nil;
  1309. }
  1310. if (!self.customActionSequenceView) {
  1311. [actionSequenceViewConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[actionSequenceView]-0-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(actionSequenceView)]];
  1312. } else {
  1313. if (_customViewSize.width) {
  1314. CGFloat maxWidth = [self maxWidth];
  1315. if (_customViewSize.width > maxWidth) _customViewSize.width = maxWidth;
  1316. [actionSequenceViewConstraints addObject:[NSLayoutConstraint constraintWithItem:actionSequenceView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:_customViewSize.width]];
  1317. }
  1318. if (_customViewSize.height) {
  1319. NSLayoutConstraint *customHeightConstraint = [NSLayoutConstraint constraintWithItem:actionSequenceView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:_customViewSize.height];
  1320. customHeightConstraint.priority = UILayoutPriorityDefaultHigh;
  1321. [actionSequenceViewConstraints addObject:customHeightConstraint];
  1322. }
  1323. [actionSequenceViewConstraints addObject:[NSLayoutConstraint constraintWithItem:actionSequenceView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:alertView attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0]];
  1324. }
  1325. if (!headerActionLine) {
  1326. [actionSequenceViewConstraints addObject:[NSLayoutConstraint constraintWithItem:actionSequenceView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:alertView attribute:NSLayoutAttributeTop multiplier:1.0 constant:0]];
  1327. }
  1328. [actionSequenceViewConstraints addObject:[NSLayoutConstraint constraintWithItem:actionSequenceView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:alertView attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0]];
  1329. [NSLayoutConstraint activateConstraints:actionSequenceViewConstraints];
  1330. self.actionSequenceViewConstraints = actionSequenceViewConstraints;
  1331. }
  1332. - (CGFloat)maxWidth {
  1333. if (self.preferredStyle == SPAlertControllerStyleAlert) {
  1334. return MIN(SP_SCREEN_WIDTH, SP_SCREEN_HEIGHT)-_minDistanceToEdges * 2;
  1335. } else {
  1336. return SP_SCREEN_WIDTH;
  1337. }
  1338. }
  1339. // 文字显示不全处理
  1340. - (void)handleIncompleteTextDisplay {
  1341. // alert样式下水平排列时如果文字显示不全则垂直排列
  1342. if (!self.isForceLayout) { // 外界没有设置排列方式
  1343. if (self.preferredStyle == SPAlertControllerStyleAlert) {
  1344. for (SPAlertAction *action in self.actions) {
  1345. // 预估按钮宽度
  1346. 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;
  1347. // 如果action的标题文字总宽度,大于按钮的contentRect的宽度,则说明水平排列会导致文字显示不全,此时垂直排列
  1348. if (action.attributedTitle) {
  1349. if (ceil([action.attributedTitle boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, SP_ACTION_HEIGHT) options:NSStringDrawingUsesLineFragmentOrigin context:nil].size.width) > preButtonWidth) {
  1350. _actionAxis = UILayoutConstraintAxisVertical;
  1351. [self updateActionAxis];
  1352. [self.actionSequenceView setNeedsUpdateConstraints];
  1353. break; // 一定要break,只要有一个按钮文字过长就垂直排列
  1354. }
  1355. } else {
  1356. if (ceil([action.title boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, SP_ACTION_HEIGHT) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:action.titleFont} context:nil].size.width) > preButtonWidth) {
  1357. _actionAxis = UILayoutConstraintAxisVertical;
  1358. [self updateActionAxis];
  1359. [self.actionSequenceView setNeedsUpdateConstraints];
  1360. break;
  1361. }
  1362. }
  1363. }
  1364. }
  1365. }
  1366. }
  1367. // 专门处理第三方IQKeyboardManager,非自定义view时禁用IQKeyboardManager移动textView/textField效果,自定义view时取消禁用
  1368. - (void)handleIQKeyboardManager {
  1369. SEL selector = NSSelectorFromString(@"sharedManager");
  1370. IMP imp = [NSClassFromString(@"IQKeyboardManager") methodForSelector:selector];
  1371. if (imp != NULL) {
  1372. NSObject *(*func)(id, SEL) = (void *)imp;
  1373. NSObject *mgr = func(NSClassFromString(@"IQKeyboardManager"), selector);
  1374. if ([mgr isKindOfClass:NSClassFromString(@"IQKeyboardManager")]) {
  1375. @try {
  1376. NSMutableSet *disabledDistanceHandlingClasses = [mgr valueForKey:@"_disabledDistanceHandlingClasses"];
  1377. NSMutableSet *disabledToolbarClasses = [mgr valueForKey:@"_disabledToolbarClasses"];
  1378. if (![disabledDistanceHandlingClasses containsObject:NSClassFromString(@"SPAlertController")]) {
  1379. [disabledDistanceHandlingClasses addObject:NSClassFromString(@"SPAlertController")];
  1380. [disabledToolbarClasses addObject:NSClassFromString(@"SPAlertController")];
  1381. }
  1382. } @catch (NSException *exception) {
  1383. NSLog(@"exception = %@",exception);
  1384. } @finally {
  1385. }
  1386. }
  1387. }
  1388. }
  1389. - (void)configureHeaderView {
  1390. if (self.image) {
  1391. self.headerView.imageLimitSize = _imageLimitSize;
  1392. self.headerView.imageView.image = _image;
  1393. self.headerView.imageView.tintColor = _imageTintColor;
  1394. [self.headerView setNeedsUpdateConstraints];
  1395. }
  1396. if(self.attributedTitle.length) {
  1397. self.headerView.titleLabel.attributedText = self.attributedTitle;
  1398. [self setupPreferredMaxLayoutWidthForLabel:self.headerView.titleLabel];
  1399. } else if(self.title.length) {
  1400. self.headerView.titleLabel.text = _title;
  1401. self.headerView.titleLabel.font = _titleFont;
  1402. self.headerView.titleLabel.textColor = _titleColor;
  1403. self.headerView.titleLabel.textAlignment = _textAlignment;
  1404. [self setupPreferredMaxLayoutWidthForLabel:self.headerView.titleLabel];
  1405. }
  1406. if (self.attributedMessage.length) {
  1407. self.headerView.messageLabel.attributedText = self.attributedMessage;
  1408. [self setupPreferredMaxLayoutWidthForLabel:self.headerView.messageLabel];
  1409. } else if (self.message.length) {
  1410. self.headerView.messageLabel.text = _message;
  1411. self.headerView.messageLabel.font = _messageFont;
  1412. self.headerView.messageLabel.textColor = _messageColor;
  1413. self.headerView.messageLabel.textAlignment = _textAlignment;
  1414. [self setupPreferredMaxLayoutWidthForLabel:self.headerView.messageLabel];
  1415. }
  1416. }
  1417. - (void)setupPreferredMaxLayoutWidthForLabel:(UILabel *)textLabel {
  1418. if (self.preferredStyle == SPAlertControllerStyleAlert) {
  1419. textLabel.preferredMaxLayoutWidth = MIN(SP_SCREEN_WIDTH, SP_SCREEN_HEIGHT) - self.minDistanceToEdges * 2 - self.headerView.contentEdgeInsets.left - self.headerView.contentEdgeInsets.right;
  1420. } else {
  1421. textLabel.preferredMaxLayoutWidth = SP_SCREEN_WIDTH - self.headerView.contentEdgeInsets.left - self.headerView.contentEdgeInsets.right;
  1422. }
  1423. }
  1424. // 这个方法是实现点击回车切换到下一个textField,如果没有下一个,会自动退出键盘. 不能在代理方法里实现,因为如果设置了代理,外界就不能成为textFiled的代理了,通知也监听不到回车
  1425. - (void)textFieldDidEndOnExit:(UITextField *)textField {
  1426. NSInteger index = [self.textFields indexOfObject:textField];
  1427. if (self.textFields.count > index + 1) {
  1428. UITextField *nextTextField = [self.textFields objectAtIndex:index + 1];
  1429. [textField resignFirstResponder];
  1430. [nextTextField becomeFirstResponder];
  1431. }
  1432. }
  1433. // 更新action的排列方式
  1434. - (void)updateActionAxis {
  1435. self.actionSequenceView.axis = _actionAxis;
  1436. if (_actionAxis == UILayoutConstraintAxisVertical) {
  1437. self.actionSequenceView.stackViewDistribution = UIStackViewDistributionFillProportionally;// 布局方式为子控件自适应内容高度
  1438. } else {
  1439. self.actionSequenceView.stackViewDistribution = UIStackViewDistributionFillEqually; // 布局方式为子控件等宽
  1440. }
  1441. }
  1442. // 该方法是保证被废弃的maxNumberOfActionHorizontalArrangementForAlert属性的有效性
  1443. - (void)setupActionAxis {
  1444. if (self.preferredStyle == SPAlertControllerStyleAlert) {
  1445. if (self.actions.count > self.maxNumberOfActionHorizontalArrangementForAlert) {
  1446. _actionAxis = UILayoutConstraintAxisVertical;
  1447. [self updateActionAxis];
  1448. } else {
  1449. _actionAxis = UILayoutConstraintAxisHorizontal;
  1450. [self updateActionAxis];
  1451. }
  1452. }
  1453. }
  1454. - (void)makeViewOffsetWithAnimated:(BOOL)animated {
  1455. if (!self.beingPresented && !self.beingDismissed) {
  1456. [self layoutAlertControllerView];
  1457. if (animated) {
  1458. [UIView animateWithDuration:0.25 animations:^{
  1459. [self.view.superview layoutIfNeeded];
  1460. }];
  1461. }
  1462. }
  1463. }
  1464. // 获取自定义view的大小
  1465. - (CGSize)sizeForCustomView:(UIView *)customView {
  1466. [customView layoutIfNeeded];
  1467. CGSize settingSize = customView.frame.size;
  1468. CGSize fittingSize = [customView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
  1469. return CGSizeMake(MAX(settingSize.width, fittingSize.width), MAX(settingSize.height, fittingSize.height));
  1470. }
  1471. #pragma mark - system methods
  1472. - (void)loadView {
  1473. // 重新创建self.view,这样可以采用自己的一套布局,轻松改变控制器view的大小
  1474. self.view = self.alertControllerView;
  1475. }
  1476. - (void)viewDidLoad {
  1477. [super viewDidLoad];
  1478. [self configureHeaderView];
  1479. self.needDialogBlur = _needDialogBlur;
  1480. self.automaticallyAdjustsScrollViewInsets = NO;
  1481. }
  1482. - (void)viewWillAppear:(BOOL)animated {
  1483. [super viewWillAppear:animated];
  1484. [self handleIQKeyboardManager];
  1485. if (!_isForceOffset && !_customAlertView && !_customHeaderView && !_customActionSequenceView && !_componentView) {
  1486. // 监听键盘改变frame,键盘frame改变需要移动对话框
  1487. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardFrameWillChange:) name:UIKeyboardWillChangeFrameNotification object:nil];
  1488. }
  1489. if (self.textFields.count) {
  1490. UITextField *firstTextfield = [self.textFields firstObject];
  1491. if (!firstTextfield.isFirstResponder) {
  1492. [firstTextfield becomeFirstResponder];
  1493. }
  1494. }
  1495. }
  1496. - (void)viewWillLayoutSubviews {
  1497. [super viewWillLayoutSubviews];
  1498. // 屏幕旋转后宽高发生了交换,头部的label最大宽度需要重新计算
  1499. [self setupPreferredMaxLayoutWidthForLabel:self.headerView.titleLabel];
  1500. [self setupPreferredMaxLayoutWidthForLabel:self.headerView.messageLabel];
  1501. // 对自己创建的alertControllerView布局,在这个方法里,self.view才有父视图,有父视图才能改变其约束
  1502. [self layoutAlertControllerView];
  1503. [self layoutChildViews];
  1504. if (self.preferredStyle == SPAlertControllerStyleActionSheet) {
  1505. [self setCornerRadius:_cornerRadius];
  1506. }
  1507. }
  1508. - (void)viewDidLayoutSubviews {
  1509. [super viewDidLayoutSubviews];
  1510. [self handleIncompleteTextDisplay];
  1511. }
  1512. - (void)viewDidAppear:(BOOL)animated {
  1513. [super viewDidAppear:animated];
  1514. }
  1515. - (void)viewWillDisappear:(BOOL)animated {
  1516. [super viewWillDisappear:animated];
  1517. }
  1518. - (void)viewDidDisappear:(BOOL)animated {
  1519. [super viewDidDisappear:animated];
  1520. }
  1521. - (void)dealloc {
  1522. [[NSNotificationCenter defaultCenter] removeObserver:self];
  1523. }
  1524. #pragma mark - 键盘通知
  1525. - (void)keyboardFrameWillChange:(NSNotification *)notification {
  1526. if (!_isForceOffset && (_offsetForAlert.y == 0.0 || _textFields.lastObject.isFirstResponder)) {
  1527. CGRect keyboardEndFrame = [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
  1528. CGFloat keyboardEndY = keyboardEndFrame.origin.y;
  1529. CGFloat diff = fabs((SP_SCREEN_HEIGHT - keyboardEndY) * 0.5);
  1530. _offsetForAlert.y = -diff;
  1531. [self makeViewOffsetWithAnimated:YES];
  1532. }
  1533. }
  1534. #pragma mark - setterx
  1535. - (void)setTitle:(NSString *)title {
  1536. _title = title;
  1537. if (self.isViewLoaded) { // 如果条件为真,说明外界在对title赋值之前就已经使用了self.view,先走了viewDidLoad方法,如果先走的viewDidLoad,需要在title的setter方法中重新设置数据,以下setter方法中的条件同理
  1538. self.headerView.titleLabel.text = title;
  1539. // 文字发生变化后再更新布局,这里更新布局也不是那么重要,因为headerView中的布局方法只有当SPAlertController被present后才会走一次,而那时候,一般title,titleFont、message、messageFont等都是最新值,这里防止的是:在SPAlertController被present后的某个时刻再去设置title,titleFont等,我们要更新布局
  1540. if (self.presentationController.presentingViewController) { // 这个if条件的意思是当SPAlertController被present后的某个时刻设置了title,如果在present之前设置的就不用更新,系统会主动更新
  1541. [self.headerView setNeedsUpdateConstraints];
  1542. }
  1543. }
  1544. }
  1545. - (void)setTitleFont:(UIFont *)titleFont {
  1546. _titleFont = titleFont;
  1547. if (self.isViewLoaded) {
  1548. self.headerView.titleLabel.font = titleFont;
  1549. if (self.presentationController.presentingViewController) {
  1550. [self.headerView setNeedsUpdateConstraints];
  1551. }
  1552. }
  1553. }
  1554. - (void)setTitleColor:(UIColor *)titleColor {
  1555. _titleColor = titleColor;
  1556. if (self.isViewLoaded) {
  1557. self.headerView.titleLabel.textColor = titleColor;
  1558. }
  1559. }
  1560. - (void)setMessage:(NSString *)message {
  1561. _message = message;
  1562. if (self.isViewLoaded) {
  1563. self.headerView.messageLabel.text = message;
  1564. if (self.presentationController.presentingViewController) {
  1565. [self.headerView setNeedsUpdateConstraints];
  1566. }
  1567. }
  1568. }
  1569. - (void)setMessageFont:(UIFont *)messageFont {
  1570. _messageFont = messageFont;
  1571. if (self.isViewLoaded) {
  1572. self.headerView.messageLabel.font = messageFont;
  1573. if (self.presentationController.presentingViewController) {
  1574. [self.headerView setNeedsUpdateConstraints];
  1575. }
  1576. }
  1577. }
  1578. - (void)setMessageColor:(UIColor *)messageColor {
  1579. _messageColor = messageColor;
  1580. if (self.isViewLoaded) {
  1581. self.headerView.messageLabel.textColor = messageColor;
  1582. }
  1583. }
  1584. - (void)setTextAlignment:(NSTextAlignment)textAlignment {
  1585. _textAlignment = textAlignment;
  1586. self.headerView.titleLabel.textAlignment = _textAlignment;
  1587. self.headerView.messageLabel.textAlignment = _textAlignment;
  1588. }
  1589. - (void)setIcon:(UIImage *)image {
  1590. _image = image;
  1591. if (self.isViewLoaded) {
  1592. self.headerView.imageView.image = _image;
  1593. if (self.presentationController.presentingViewController) {
  1594. [self.headerView setNeedsUpdateConstraints];
  1595. }
  1596. }
  1597. }
  1598. - (void)setIconLimitSize:(CGSize)imageLimitSize {
  1599. _imageLimitSize = imageLimitSize;
  1600. if (self.isViewLoaded) {
  1601. self.headerView.imageLimitSize = _imageLimitSize;
  1602. if (self.presentationController.presentingViewController) {
  1603. [self.headerView setNeedsUpdateConstraints];
  1604. }
  1605. }
  1606. }
  1607. - (void)setImageTintColor:(UIColor *)imageTintColor {
  1608. _imageTintColor = imageTintColor;
  1609. if (self.isViewLoaded) {
  1610. self.headerView.imageView.tintColor = imageTintColor;
  1611. }
  1612. }
  1613. - (void)setAttributedTitle:(NSAttributedString *)attributedTitle {
  1614. _attributedTitle = attributedTitle;
  1615. if (self.isViewLoaded) {
  1616. self.headerView.titleLabel.attributedText = _attributedTitle;
  1617. if (self.presentationController.presentingViewController) {
  1618. [self.headerView setNeedsUpdateConstraints];
  1619. }
  1620. }
  1621. }
  1622. - (void)setAttributedMessage:(NSAttributedString *)attributedMessage {
  1623. _attributedMessage = attributedMessage;
  1624. if (self.isViewLoaded) {
  1625. self.headerView.messageLabel.attributedText = _attributedMessage;
  1626. if (self.presentationController.presentingViewController) {
  1627. [self.headerView setNeedsUpdateConstraints];
  1628. }
  1629. }
  1630. }
  1631. // 该属性3.0版本开始被废弃
  1632. - (void)setMaxMarginForAlert:(CGFloat)maxMarginForAlert {
  1633. _maxMarginForAlert = maxMarginForAlert;
  1634. self.minDistanceToEdges = _maxMarginForAlert;
  1635. }
  1636. // 该属性3.0版本开始被废弃
  1637. - (void)setMaxTopMarginForActionSheet:(CGFloat)maxTopMarginForActionSheet {
  1638. _maxTopMarginForActionSheet = maxTopMarginForActionSheet;
  1639. self.minDistanceToEdges = _maxTopMarginForActionSheet;
  1640. }
  1641. - (void)setMinDistanceToEdges:(CGFloat)minDistanceToEdges {
  1642. _minDistanceToEdges = minDistanceToEdges;
  1643. if (self.isViewLoaded) {
  1644. [self setupPreferredMaxLayoutWidthForLabel:self.headerView.titleLabel];
  1645. [self setupPreferredMaxLayoutWidthForLabel:self.headerView.messageLabel];
  1646. if (self.presentationController.presentingViewController) {
  1647. [self layoutAlertControllerView];
  1648. [self.headerView setNeedsUpdateConstraints];
  1649. [self.actionSequenceView setNeedsUpdateConstraints];
  1650. }
  1651. }
  1652. }
  1653. - (void)setCornerRadius:(CGFloat)cornerRadius {
  1654. _cornerRadius = cornerRadius;
  1655. if (self.preferredStyle == SPAlertControllerStyleAlert) {
  1656. self.containerView.layer.cornerRadius = _cornerRadius;
  1657. self.containerView.layer.masksToBounds = YES;
  1658. } else {
  1659. if (_cornerRadius > 0.0) {
  1660. UIRectCorner corner = UIRectCornerTopLeft | UIRectCornerTopRight;
  1661. switch (_animationType) {
  1662. case SPAlertAnimationTypeFromBottom:
  1663. corner = UIRectCornerTopLeft | UIRectCornerTopRight;
  1664. break;
  1665. case SPAlertAnimationTypeFromTop:
  1666. corner = UIRectCornerBottomLeft | UIRectCornerBottomRight;
  1667. break;
  1668. case SPAlertAnimationTypeFromLeft:
  1669. corner = UIRectCornerTopRight | UIRectCornerBottomRight;
  1670. break;
  1671. case SPAlertAnimationTypeFromRight:
  1672. corner = UIRectCornerTopLeft | UIRectCornerBottomLeft;
  1673. break;
  1674. default:
  1675. break;
  1676. }
  1677. CAShapeLayer *maskLayer = (CAShapeLayer *)_containerView.layer.mask;
  1678. maskLayer.path = [UIBezierPath bezierPathWithRoundedRect:_containerView.bounds byRoundingCorners:corner cornerRadii:CGSizeMake(_cornerRadius, _cornerRadius)].CGPath;
  1679. maskLayer.frame = _containerView.bounds;
  1680. } else {
  1681. _containerView.layer.mask = nil;
  1682. }
  1683. }
  1684. }
  1685. - (void)setCornerRadiusForAlert:(CGFloat)cornerRadiusForAlert {
  1686. _cornerRadiusForAlert = cornerRadiusForAlert;
  1687. _cornerRadius = cornerRadiusForAlert;
  1688. if (self.preferredStyle == SPAlertControllerStyleAlert) {
  1689. self.containerView.layer.cornerRadius = _cornerRadiusForAlert;
  1690. self.containerView.layer.masksToBounds = YES;
  1691. }
  1692. }
  1693. // 此属性3.0版本开始被废弃
  1694. - (void)setMaxNumberOfActionHorizontalArrangementForAlert:(NSInteger)maxNumberOfActionHorizontalArrangementForAlert {
  1695. _maxNumberOfActionHorizontalArrangementForAlert = maxNumberOfActionHorizontalArrangementForAlert;
  1696. // 被废弃的maxNumberOfActionHorizontalArrangementForAlert属性需要的方法
  1697. [self setupActionAxis];
  1698. }
  1699. - (void)setActionAxis:(UILayoutConstraintAxis)actionAxis {
  1700. _actionAxis = actionAxis;
  1701. // 调用该setter方法则认为是强制布局,该setter方法只有外界能调,这样才能判断外界有没有调用actionAxis的setter方法,从而是否按照外界的指定布局方式进行布局
  1702. _isForceLayout = YES;
  1703. [self updateActionAxis];
  1704. }
  1705. - (void)setOffsetForAlert:(CGPoint)offsetForAlert {
  1706. _offsetForAlert = offsetForAlert;
  1707. _isForceOffset = YES;
  1708. [self makeViewOffsetWithAnimated:NO];
  1709. }
  1710. // 被废弃
  1711. - (void)setOffsetYForAlert:(CGFloat)offsetYForAlert {
  1712. _offsetYForAlert = offsetYForAlert;
  1713. _offsetForAlert.y = _offsetYForAlert;
  1714. _isForceOffset = YES;
  1715. }
  1716. - (void)setNeedDialogBlur:(BOOL)needDialogBlur {
  1717. _needDialogBlur = needDialogBlur;
  1718. if (_needDialogBlur) {
  1719. self.containerView.backgroundColor = [UIColor clearColor];
  1720. if (!self.dimmingKnockoutBackdropView) {
  1721. self.dimmingKnockoutBackdropView = [NSClassFromString(@"_UIDimmingKnockoutBackdropView") alloc];
  1722. if (self.dimmingKnockoutBackdropView) {
  1723. // 下面4行相当于self.dimmingKnockoutBackdropView = [self.dimmingKnockoutBackdropView performSelector:NSSelectorFromString(@"initWithStyle:") withObject:@(UIBlurEffectStyleLight)];
  1724. SEL selector = NSSelectorFromString(@"initWithStyle:");
  1725. IMP imp = [self.dimmingKnockoutBackdropView methodForSelector:selector];
  1726. if (imp != NULL) {
  1727. UIView *(*func)(id, SEL,UIBlurEffectStyle) = (void *)imp;
  1728. self.dimmingKnockoutBackdropView = func(self.dimmingKnockoutBackdropView, selector, UIBlurEffectStyleLight);
  1729. self.dimmingKnockoutBackdropView.frame = self.containerView.bounds;
  1730. self.dimmingKnockoutBackdropView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
  1731. [self.containerView insertSubview:self.dimmingKnockoutBackdropView atIndex:0];
  1732. }
  1733. } else { // 这个else是防止假如_UIDimmingKnockoutBackdropView这个类不存在了的时候,做一个备案
  1734. UIBlurEffect *blur = [UIBlurEffect effectWithStyle:UIBlurEffectStyleExtraLight];
  1735. self.dimmingKnockoutBackdropView = [[UIVisualEffectView alloc] initWithEffect:blur];
  1736. self.dimmingKnockoutBackdropView.frame = self.containerView.bounds;
  1737. self.dimmingKnockoutBackdropView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
  1738. [self.containerView insertSubview:self.dimmingKnockoutBackdropView atIndex:0];
  1739. }
  1740. }
  1741. } else {
  1742. [self.dimmingKnockoutBackdropView removeFromSuperview];
  1743. self.dimmingKnockoutBackdropView = nil;
  1744. if (_customAlertView) {
  1745. self.containerView.backgroundColor = [UIColor clearColor];
  1746. } else {
  1747. self.containerView.backgroundColor = [SPColorStyle lightWhite_DarkBlackColor];
  1748. }
  1749. }
  1750. }
  1751. #pragma mark - lazy load
  1752. - (UIView *)alertControllerView {
  1753. if (!_alertControllerView) {
  1754. UIView *alertControllerView = [[UIView alloc] init];
  1755. alertControllerView.translatesAutoresizingMaskIntoConstraints = NO;
  1756. _alertControllerView = alertControllerView;
  1757. }
  1758. return _alertControllerView;
  1759. }
  1760. - (UIView *)containerView {
  1761. if (!_containerView) {
  1762. UIView *containerView = [[UIView alloc] init];
  1763. containerView.frame = self.alertControllerView.bounds;
  1764. containerView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
  1765. if (_preferredStyle == SPAlertControllerStyleAlert) {
  1766. containerView.layer.cornerRadius = _cornerRadius;
  1767. containerView.layer.masksToBounds = YES;
  1768. } else {
  1769. if (_cornerRadius > 0.0) {
  1770. CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
  1771. containerView.layer.mask = maskLayer;
  1772. }
  1773. }
  1774. [self.alertControllerView addSubview:containerView];
  1775. _containerView = containerView;
  1776. }
  1777. return _containerView;
  1778. }
  1779. - (UIView *)alertView {
  1780. if (!_alertView) {
  1781. UIView *alertView = [[UIView alloc] init];
  1782. alertView.frame = self.alertControllerView.bounds;
  1783. alertView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
  1784. if (!self.customAlertView) {
  1785. [self.containerView addSubview:alertView];
  1786. }
  1787. _alertView = alertView;
  1788. }
  1789. return _alertView;
  1790. }
  1791. - (UIView *)customAlertView {
  1792. // customAlertView有值但是没有父view
  1793. if (_customAlertView && !_customAlertView.superview) {
  1794. if (CGSizeEqualToSize(_customViewSize, CGSizeZero)) {
  1795. // 获取_customAlertView的大小
  1796. _customViewSize = [self sizeForCustomView:_customAlertView];
  1797. }
  1798. // 必须在在下面2行代码之前获取_customViewSize
  1799. _customAlertView.frame = self.alertControllerView.bounds;
  1800. _customAlertView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
  1801. [self.containerView addSubview:_customAlertView];
  1802. }
  1803. return _customAlertView;
  1804. }
  1805. - (SPInterfaceHeaderScrollView *)headerView {
  1806. if (!_headerView) {
  1807. SPInterfaceHeaderScrollView *headerView = [[SPInterfaceHeaderScrollView alloc] init];
  1808. headerView.backgroundColor = [SPColorStyle normalColor];
  1809. headerView.translatesAutoresizingMaskIntoConstraints = NO;
  1810. __weak typeof(self) weakSelf = self;
  1811. headerView.headerViewSfeAreaDidChangBlock = ^{
  1812. [weakSelf setupPreferredMaxLayoutWidthForLabel:weakSelf.headerView.titleLabel];
  1813. [weakSelf setupPreferredMaxLayoutWidthForLabel:weakSelf.headerView.messageLabel];
  1814. };
  1815. if (!self.customHeaderView) {
  1816. if ((self.title.length || self.attributedTitle.length || self.message.length || self.attributedMessage.length || self.textFields.count || self.image)) {
  1817. [self.alertView addSubview:headerView];
  1818. }
  1819. }
  1820. _headerView = headerView;
  1821. }
  1822. return _headerView;
  1823. }
  1824. - (UIView *)customHeaderView {
  1825. // _customHeaderView有值但是没有父view
  1826. if (_customHeaderView && !_customHeaderView.superview) {
  1827. // 获取_customHeaderView的大小
  1828. if (CGSizeEqualToSize(_customViewSize, CGSizeZero)) {
  1829. // 获取_customHeaderView的大小
  1830. _customViewSize = [self sizeForCustomView:_customHeaderView];
  1831. }
  1832. _customHeaderView.translatesAutoresizingMaskIntoConstraints = NO;
  1833. [self.alertView addSubview:_customHeaderView];
  1834. }
  1835. return _customHeaderView;
  1836. }
  1837. - (SPInterfaceActionSequenceView *)actionSequenceView {
  1838. if (!_actionSequenceView) {
  1839. SPInterfaceActionSequenceView *actionSequenceView = [[SPInterfaceActionSequenceView alloc] init];
  1840. actionSequenceView.translatesAutoresizingMaskIntoConstraints = NO;
  1841. __weak typeof(self) weakSelf = self;
  1842. actionSequenceView.buttonClickedInActionViewBlock = ^(NSInteger index) {
  1843. [weakSelf dismissViewControllerAnimated:YES completion:nil];
  1844. SPAlertAction *action = weakSelf.actions[index];
  1845. if (action.handler) {
  1846. action.handler(action);
  1847. }
  1848. };
  1849. if (self.actions.count && !self.customActionSequenceView) {
  1850. [self.alertView addSubview:actionSequenceView];
  1851. }
  1852. _actionSequenceView = actionSequenceView;
  1853. }
  1854. return _actionSequenceView;
  1855. }
  1856. - (UIView *)customActionSequenceView {
  1857. // _customActionSequenceView有值但是没有父view
  1858. if (_customActionSequenceView && !_customActionSequenceView.superview) {
  1859. // 获取_customHeaderView的大小
  1860. if (CGSizeEqualToSize(_customViewSize, CGSizeZero)) {
  1861. // 获取_customActionSequenceView的大小
  1862. _customViewSize = [self sizeForCustomView:_customActionSequenceView];
  1863. }
  1864. _customActionSequenceView.translatesAutoresizingMaskIntoConstraints = NO;
  1865. [self.alertView addSubview:_customActionSequenceView];
  1866. }
  1867. return _customActionSequenceView;
  1868. }
  1869. - (SPInterfaceActionItemSeparatorView *)headerActionLine {
  1870. if (!_headerActionLine) {
  1871. SPInterfaceActionItemSeparatorView *headerActionLine = [[SPInterfaceActionItemSeparatorView alloc] init];
  1872. headerActionLine.translatesAutoresizingMaskIntoConstraints = NO;
  1873. if ((self.headerView.superview || self.customHeaderView.superview) && (self.actionSequenceView.superview || self.customActionSequenceView.superview)) {
  1874. [self.alertView addSubview:headerActionLine];
  1875. }
  1876. _headerActionLine = headerActionLine;
  1877. }
  1878. return _headerActionLine;
  1879. }
  1880. - (UIView *)componentView {
  1881. if (_componentView && !_componentView.superview) {
  1882. NSAssert(self.headerActionLine.superview, @"Due to the -componentView is added between the -head and the -action section, the -head and -action must exist together");
  1883. // 获取_componentView的大小
  1884. if (CGSizeEqualToSize(_customViewSize, CGSizeZero)) {
  1885. // 获取_componentView的大小
  1886. _customViewSize = [self sizeForCustomView:_componentView];
  1887. }
  1888. _componentView.translatesAutoresizingMaskIntoConstraints = NO;
  1889. [self.alertView addSubview:_componentView];
  1890. }
  1891. return _componentView;
  1892. }
  1893. - (SPInterfaceActionItemSeparatorView *)componentActionLine {
  1894. if (!_componentActionLine) {
  1895. SPInterfaceActionItemSeparatorView *componentActionLine = [[SPInterfaceActionItemSeparatorView alloc] init];
  1896. componentActionLine.translatesAutoresizingMaskIntoConstraints = NO;
  1897. // 必须组件view和action部分同时存在
  1898. if (self.componentView.superview && (self.actionSequenceView.superview || self.customActionSequenceView.superview)) {
  1899. [self.alertView addSubview:componentActionLine];
  1900. }
  1901. _componentActionLine = componentActionLine;
  1902. }
  1903. return _componentActionLine;
  1904. }
  1905. - (NSArray<SPAlertAction *> *)actions {
  1906. if (!_actions) {
  1907. _actions = [NSArray array];
  1908. }
  1909. return _actions;
  1910. }
  1911. - (NSArray<UITextField *> *)textFields {
  1912. if (!_textFields) {
  1913. _textFields = [NSArray array];
  1914. }
  1915. return _textFields;
  1916. }
  1917. - (NSMutableArray *)otherActions {
  1918. if (!_otherActions) {
  1919. _otherActions = [[NSMutableArray alloc] init];
  1920. }
  1921. return _otherActions;
  1922. }
  1923. #pragma mark - UIViewControllerTransitioningDelegate
  1924. - (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source {
  1925. return [SPAlertAnimation animationIsPresenting:YES];
  1926. }
  1927. - (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed {
  1928. [self.view endEditing:YES];
  1929. return [SPAlertAnimation animationIsPresenting:NO];
  1930. }
  1931. - (nullable UIPresentationController *)presentationControllerForPresentedViewController:(UIViewController *)presented presentingViewController:(nullable UIViewController *)presenting sourceViewController:(UIViewController *)source NS_AVAILABLE_IOS(8_0) {
  1932. return [[SPAlertPresentationController alloc] initWithPresentedViewController:presented presentingViewController:presenting];
  1933. }
  1934. #pragma mark - 被废弃的方法
  1935. + (instancetype)alertControllerWithTitle:(nullable NSString *)title message:(nullable NSString *)message preferredStyle:(SPAlertControllerStyle)preferredStyle animationType:(SPAlertAnimationType)animationType customView:(UIView *)customView {
  1936. SPAlertController *alertVc = [[SPAlertController alloc] initWithTitle:nil message:nil customAlertView:customView customHeaderView:nil customActionSequenceView:nil componentView:nil preferredStyle:preferredStyle animationType:animationType];
  1937. return alertVc;
  1938. }
  1939. + (instancetype)alertControllerWithPreferredStyle:(SPAlertControllerStyle)preferredStyle animationType:(SPAlertAnimationType)animationType customHeaderView:(nullable UIView *)customHeaderView {
  1940. SPAlertController *alertVc = [[SPAlertController alloc] initWithTitle:nil message:nil customAlertView:nil customHeaderView:customHeaderView customActionSequenceView:nil componentView:nil preferredStyle:preferredStyle animationType:animationType];
  1941. return alertVc;
  1942. }
  1943. + (instancetype)alertControllerWithTitle:(NSString *)title message:(NSString *)message preferredStyle:(SPAlertControllerStyle)preferredStyle animationType:(SPAlertAnimationType)animationType customCenterView:(UIView *)customCenterView {
  1944. SPAlertController *alertVc = [[SPAlertController alloc] initWithTitle:title message:message customAlertView:nil customHeaderView:nil customActionSequenceView:nil componentView:customCenterView preferredStyle:preferredStyle animationType:animationType];
  1945. return alertVc;
  1946. }
  1947. + (instancetype)alertControllerWithTitle:(NSString *)title message:(NSString *)message preferredStyle:(SPAlertControllerStyle)preferredStyle animationType:(SPAlertAnimationType)animationType customFooterView:(UIView *)customFooterView {
  1948. SPAlertController *alertVc = [[SPAlertController alloc] initWithTitle:title message:message customAlertView:nil customHeaderView:nil customActionSequenceView:customFooterView componentView:nil preferredStyle:preferredStyle animationType:animationType];
  1949. return alertVc;
  1950. }
  1951. @end
  1952. #pragma mark ---------------------------- SPAlertController end --------------------------------
  1953. @interface SPOverlayView: UIView
  1954. @property (nonatomic, strong) UIView *presentedView;
  1955. @property (nonatomic, strong) UIVisualEffectView *effectView;
  1956. @end
  1957. @implementation SPOverlayView
  1958. - (instancetype)initWithFrame:(CGRect)frame {
  1959. if (self = [super initWithFrame:frame]) {
  1960. }
  1961. return self;
  1962. }
  1963. - (void)setAppearanceStyle:(UIBlurEffectStyle)appearanceStyle alpha:(CGFloat)alpha {
  1964. switch (appearanceStyle) {
  1965. case -1: {
  1966. [self.effectView removeFromSuperview];
  1967. self.effectView = nil;
  1968. if (alpha < 0) {
  1969. alpha = 0.5;
  1970. }
  1971. self.backgroundColor = [UIColor colorWithWhite:0 alpha:alpha];
  1972. self.alpha = 0;
  1973. }
  1974. break;
  1975. default:{
  1976. UIBlurEffect *blur = [UIBlurEffect effectWithStyle:appearanceStyle];
  1977. [self createVisualEffectViewWithBlur:blur alpha:alpha];
  1978. }
  1979. }
  1980. }
  1981. - (void)createVisualEffectViewWithBlur:(UIBlurEffect *)blur alpha:(CGFloat)alpha {
  1982. self.backgroundColor = [UIColor clearColor];
  1983. UIVisualEffectView *effectView = [[UIVisualEffectView alloc] initWithEffect:blur];
  1984. effectView.frame = self.bounds;
  1985. effectView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
  1986. effectView.userInteractionEnabled = NO;
  1987. effectView.alpha = alpha;
  1988. [self addSubview:effectView];
  1989. _effectView = effectView;
  1990. }
  1991. @end
  1992. #pragma mark ---------------------------- SPAlertPresentationController begin --------------------------------
  1993. @interface SPAlertPresentationController()
  1994. @property (nonatomic, strong) SPOverlayView *overlayView;
  1995. @end
  1996. @implementation SPAlertPresentationController
  1997. - (instancetype)initWithPresentedViewController:(UIViewController *)presentedViewController presentingViewController:(UIViewController *)presentingViewController {
  1998. if (self = [super initWithPresentedViewController:presentedViewController presentingViewController:presentingViewController]) {
  1999. }
  2000. return self;
  2001. }
  2002. - (void)containerViewWillLayoutSubviews {
  2003. [super containerViewWillLayoutSubviews];
  2004. self.overlayView.frame = self.containerView.bounds;
  2005. }
  2006. - (void)containerViewDidLayoutSubviews {
  2007. [super containerViewDidLayoutSubviews];
  2008. }
  2009. - (void)presentationTransitionWillBegin {
  2010. [super presentationTransitionWillBegin];
  2011. SPAlertController *alertController = (SPAlertController *)self.presentedViewController;
  2012. [self.overlayView setAppearanceStyle:alertController.backgroundViewAppearanceStyle alpha:alertController.backgroundViewAlpha];
  2013. // 遮罩的alpha值从0~1变化,UIViewControllerTransitionCoordinator协是一个过渡协调器,当执行模态过渡或push过渡时,可以对视图中的其他部分做动画
  2014. id <UIViewControllerTransitionCoordinator> coordinator = [self.presentedViewController transitionCoordinator];
  2015. if (coordinator) {
  2016. [coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {
  2017. self.overlayView.alpha = 1.0;
  2018. } completion:nil];
  2019. } else {
  2020. self.overlayView.alpha = 1.0;
  2021. }
  2022. if ([alertController.delegate respondsToSelector:@selector(willPresentAlertController:)]) {
  2023. [alertController.delegate willPresentAlertController:alertController];
  2024. } else if ([alertController.delegate respondsToSelector:@selector(sp_alertControllerWillShow:)]) { // 支持老版本
  2025. [alertController.delegate sp_alertControllerWillShow:alertController];
  2026. }
  2027. }
  2028. - (void)presentationTransitionDidEnd:(BOOL)completed {
  2029. [super presentationTransitionDidEnd:completed];
  2030. SPAlertController *alertController = (SPAlertController *)self.presentedViewController;
  2031. if ([alertController.delegate respondsToSelector:@selector(didPresentAlertController:)]) {
  2032. [alertController.delegate didPresentAlertController:alertController];
  2033. } else if ([alertController.delegate respondsToSelector:@selector(sp_alertControllerDidShow:)]) { // 支持老版本
  2034. [alertController.delegate sp_alertControllerDidShow:alertController];
  2035. }
  2036. }
  2037. - (void)dismissalTransitionWillBegin {
  2038. [super dismissalTransitionWillBegin];
  2039. // 遮罩的alpha值从1~0变化,UIViewControllerTransitionCoordinator协议执行动画可以保证和转场动画同步
  2040. id <UIViewControllerTransitionCoordinator> coordinator = [self.presentedViewController transitionCoordinator];
  2041. if (coordinator) {
  2042. [coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {
  2043. self.overlayView.alpha = 0.0;
  2044. } completion:nil];
  2045. } else {
  2046. self.overlayView.alpha = 0.0;
  2047. }
  2048. SPAlertController *alertController = (SPAlertController *)self.presentedViewController;
  2049. if ([alertController.delegate respondsToSelector:@selector(willDismissAlertController:)]) {
  2050. [alertController.delegate willDismissAlertController:alertController];
  2051. } else if ([alertController.delegate respondsToSelector:@selector(sp_alertControllerWillHide:)]) { // 支持老版本
  2052. [alertController.delegate sp_alertControllerWillHide:alertController];
  2053. }
  2054. }
  2055. - (void)dismissalTransitionDidEnd:(BOOL)completed {
  2056. [super dismissalTransitionDidEnd:completed];
  2057. if (completed) {
  2058. [_overlayView removeFromSuperview];
  2059. _overlayView = nil;
  2060. }
  2061. SPAlertController *alertController = (SPAlertController *)self.presentedViewController;
  2062. if ([alertController.delegate respondsToSelector:@selector(didDismissAlertController:)]) {
  2063. [alertController.delegate didDismissAlertController:alertController];
  2064. } else if ([alertController.delegate respondsToSelector:@selector(sp_alertControllerDidHide:)]) { // 支持老版本
  2065. [alertController.delegate sp_alertControllerDidHide:alertController];
  2066. }
  2067. }
  2068. - (CGRect)frameOfPresentedViewInContainerView{
  2069. return self.presentedView.frame;
  2070. }
  2071. - (void)tapOverlayView {
  2072. SPAlertController *alertController = (SPAlertController *)self.presentedViewController;
  2073. if (alertController.tapBackgroundViewDismiss) {
  2074. [alertController dismissViewControllerAnimated:YES completion:^{}];
  2075. }
  2076. }
  2077. - (void)dealloc {
  2078. [[NSNotificationCenter defaultCenter] removeObserver:self];
  2079. }
  2080. - (SPOverlayView *)overlayView {
  2081. if (!_overlayView) {
  2082. _overlayView = [[SPOverlayView alloc] init];
  2083. _overlayView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
  2084. UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapOverlayView)];
  2085. [_overlayView addGestureRecognizer:tap];
  2086. [self.containerView addSubview:_overlayView];
  2087. }
  2088. return _overlayView;
  2089. }
  2090. @end
  2091. #pragma mark ---------------------------- SPAlertPresentationController end --------------------------------
  2092. #pragma mark ---------------------------- SPAlertAnimation begin --------------------------------
  2093. @interface SPAlertAnimation()
  2094. @property (nonatomic, assign) BOOL presenting;
  2095. @end
  2096. @implementation SPAlertAnimation
  2097. + (instancetype)animationIsPresenting:(BOOL)isPresenting {
  2098. return [[self alloc] initWithPresenting:isPresenting];
  2099. }
  2100. - (instancetype)initWithPresenting:(BOOL)isPresenting {
  2101. if (self = [super init]) {
  2102. self.presenting = isPresenting;
  2103. }
  2104. return self;
  2105. }
  2106. #pragma mark - UIViewControllerAnimatedTransitioning
  2107. - (NSTimeInterval)transitionDuration:(nullable id <UIViewControllerContextTransitioning>)transitionContext {
  2108. return 0.25f;
  2109. }
  2110. - (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext {
  2111. if (self.presenting) {
  2112. [self presentAnimationTransition:transitionContext];
  2113. } else {
  2114. [self dismissAnimationTransition:transitionContext];
  2115. }
  2116. }
  2117. - (void)presentAnimationTransition:(id<UIViewControllerContextTransitioning>)transitionContext {
  2118. SPAlertController *alertController = (SPAlertController *)[transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
  2119. switch (alertController.animationType) {
  2120. case SPAlertAnimationTypeRaiseUp:
  2121. case SPAlertAnimationTypeFromBottom:
  2122. [self raiseUpWhenPresentForController:alertController transition:transitionContext];
  2123. break;
  2124. case SPAlertAnimationTypeFromRight:
  2125. [self fromRightWhenPresentForController:alertController transition:transitionContext];
  2126. break;
  2127. case SPAlertAnimationTypeDropDown:
  2128. case SPAlertAnimationTypeFromTop:
  2129. [self dropDownWhenPresentForController:alertController transition:transitionContext];
  2130. break;
  2131. case SPAlertAnimationTypeFromLeft:
  2132. [self fromLeftWhenPresentForController:alertController transition:transitionContext];
  2133. break;
  2134. case SPAlertAnimationTypeAlpha:
  2135. case SPAlertAnimationTypeFade:
  2136. [self alphaWhenPresentForController:alertController transition:transitionContext];
  2137. break;
  2138. case SPAlertAnimationTypeExpand:
  2139. [self expandWhenPresentForController:alertController transition:transitionContext];
  2140. break;
  2141. case SPAlertAnimationTypeShrink:
  2142. [self shrinkWhenPresentForController:alertController transition:transitionContext];
  2143. break;
  2144. case SPAlertAnimationTypeNone:
  2145. [self noneWhenPresentForController:alertController transition:transitionContext];
  2146. break;
  2147. default:
  2148. break;
  2149. }
  2150. }
  2151. - (void)dismissAnimationTransition:(id<UIViewControllerContextTransitioning>)transitionContext {
  2152. SPAlertController *alertController = (SPAlertController *)[transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
  2153. if ([alertController isKindOfClass:[SPAlertController class]]) {
  2154. switch (alertController.animationType) {
  2155. case SPAlertAnimationTypeRaiseUp:
  2156. case SPAlertAnimationTypeFromBottom:
  2157. [self dismissCorrespondingRaiseUpForController:alertController transition:transitionContext];
  2158. break;
  2159. case SPAlertAnimationTypeFromRight:
  2160. [self dismissCorrespondingFromRightForController:alertController transition:transitionContext];
  2161. break;
  2162. case SPAlertAnimationTypeFromLeft:
  2163. [self dismissCorrespondingFromLeftForController:alertController transition:transitionContext];
  2164. break;
  2165. case SPAlertAnimationTypeDropDown:
  2166. case SPAlertAnimationTypeFromTop:
  2167. [self dismissCorrespondingDropDownForController:alertController transition:transitionContext];
  2168. break;
  2169. case SPAlertAnimationTypeAlpha:
  2170. case SPAlertAnimationTypeFade:
  2171. [self dismissCorrespondingAlphaForController:alertController transition:transitionContext];
  2172. break;
  2173. case SPAlertAnimationTypeExpand:
  2174. [self dismissCorrespondingExpandForController:alertController transition:transitionContext];
  2175. break;
  2176. case SPAlertAnimationTypeShrink:
  2177. [self dismissCorrespondingShrinkForController:alertController transition:transitionContext];
  2178. break;
  2179. case SPAlertAnimationTypeNone:
  2180. [self dismissCorrespondingNoneForController:alertController transition:transitionContext];
  2181. break;
  2182. default:
  2183. break;
  2184. }
  2185. }
  2186. }
  2187. // 从底部弹出的present动画
  2188. - (void)raiseUpWhenPresentForController:(SPAlertController *)alertController transition:(id<UIViewControllerContextTransitioning>)transitionContext {
  2189. UIView *containerView = [transitionContext containerView];
  2190. // 将alertController的view添加到containerView上
  2191. [containerView addSubview:alertController.view];
  2192. // 标记需要刷新布局
  2193. [containerView setNeedsLayout];
  2194. // 在有标记刷新布局的情况下立即布局,这行代码很重要,第一:立即布局会立即调用SPAlertController的viewWillLayoutSubviews的方法,第二:立即布局后可以获取到alertController.view的frame
  2195. [containerView layoutIfNeeded];
  2196. // 这3行代码不能放在[containerView layoutIfNeeded]之前,如果放在之前,[containerView layoutIfNeeded]强制布局后会将以下设置的frame覆盖
  2197. CGRect controlViewFrame = alertController.view.frame;
  2198. controlViewFrame.origin.y = SP_SCREEN_HEIGHT;
  2199. alertController.view.frame = controlViewFrame;
  2200. [UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0.0 options:UIViewAnimationOptionCurveEaseOut animations:^{
  2201. CGRect controlViewFrame = alertController.view.frame;
  2202. if (alertController.preferredStyle == SPAlertControllerStyleActionSheet) {
  2203. controlViewFrame.origin.y = SP_SCREEN_HEIGHT-controlViewFrame.size.height;
  2204. } else {
  2205. controlViewFrame.origin.y = (SP_SCREEN_HEIGHT-controlViewFrame.size.height) / 2.0;
  2206. [self offSetCenter:alertController];
  2207. }
  2208. alertController.view.frame = controlViewFrame;
  2209. } completion:^(BOOL finished) {
  2210. [transitionContext completeTransition:finished];
  2211. [alertController layoutAlertControllerView];
  2212. }];
  2213. }
  2214. // 从底部弹出对应的dismiss动画
  2215. - (void)dismissCorrespondingRaiseUpForController:(SPAlertController *)alertController transition:(id<UIViewControllerContextTransitioning>)transitionContext {
  2216. [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
  2217. CGRect controlViewFrame = alertController.view.frame;
  2218. controlViewFrame.origin.y = SP_SCREEN_HEIGHT;
  2219. alertController.view.frame = controlViewFrame;
  2220. } completion:^(BOOL finished) {
  2221. [transitionContext completeTransition:finished];
  2222. }];
  2223. }
  2224. // 从右边弹出的present动画
  2225. - (void)fromRightWhenPresentForController:(SPAlertController *)alertController transition:(id<UIViewControllerContextTransitioning>)transitionContext {
  2226. UIView *containerView = [transitionContext containerView];
  2227. // 将alertController的view添加到containerView上
  2228. [containerView addSubview:alertController.view];
  2229. // 标记需要刷新布局
  2230. [containerView setNeedsLayout];
  2231. // 在有标记刷新布局的情况下立即布局,这行代码很重要,第一:立即布局会立即调用SPAlertController的viewWillLayoutSubviews的方法,第二:立即布局后可以获取到alertController.view的frame
  2232. [containerView layoutIfNeeded];
  2233. // 这3行代码不能放在[containerView layoutIfNeeded]之前,如果放在之前,[containerView layoutIfNeeded]强制布局后会将以下设置的frame覆盖
  2234. CGRect controlViewFrame = alertController.view.frame;
  2235. controlViewFrame.origin.x = SP_SCREEN_WIDTH;
  2236. alertController.view.frame = controlViewFrame;
  2237. if (alertController.preferredStyle == SPAlertControllerStyleAlert) {
  2238. [self offSetCenter:alertController];
  2239. }
  2240. [UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0.0 options:UIViewAnimationOptionCurveEaseOut animations:^{
  2241. CGRect controlViewFrame = alertController.view.frame;
  2242. if (alertController.preferredStyle == SPAlertControllerStyleActionSheet) {
  2243. controlViewFrame.origin.x = SP_SCREEN_WIDTH-controlViewFrame.size.width;
  2244. } else {
  2245. controlViewFrame.origin.x = (SP_SCREEN_WIDTH-controlViewFrame.size.width) / 2.0;
  2246. }
  2247. alertController.view.frame = controlViewFrame;
  2248. } completion:^(BOOL finished) {
  2249. [transitionContext completeTransition:finished];
  2250. [alertController layoutAlertControllerView];
  2251. }];
  2252. }
  2253. // 从右边弹出对应的dismiss动画
  2254. - (void)dismissCorrespondingFromRightForController:(SPAlertController *)alertController transition:(id<UIViewControllerContextTransitioning>)transitionContext {
  2255. [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
  2256. CGRect controlViewFrame = alertController.view.frame;
  2257. controlViewFrame.origin.x = SP_SCREEN_WIDTH;
  2258. alertController.view.frame = controlViewFrame;
  2259. } completion:^(BOOL finished) {
  2260. [transitionContext completeTransition:finished];
  2261. }];
  2262. }
  2263. // 从左边弹出的present动画
  2264. - (void)fromLeftWhenPresentForController:(SPAlertController *)alertController transition:(id<UIViewControllerContextTransitioning>)transitionContext {
  2265. UIView *containerView = [transitionContext containerView];
  2266. // 将alertController的view添加到containerView上
  2267. [containerView addSubview:alertController.view];
  2268. // 标记需要刷新布局
  2269. [containerView setNeedsLayout];
  2270. // 在有标记刷新布局的情况下立即布局,这行代码很重要,第一:立即布局会立即调用SPAlertController的viewWillLayoutSubviews的方法,第二:立即布局后可以获取到alertController.view的frame
  2271. [containerView layoutIfNeeded];
  2272. // 这3行代码不能放在[containerView layoutIfNeeded]之前,如果放在之前,[containerView layoutIfNeeded]强制布局后会将以下设置的frame覆盖
  2273. CGRect controlViewFrame = alertController.view.frame;
  2274. controlViewFrame.origin.x = -controlViewFrame.size.width;
  2275. alertController.view.frame = controlViewFrame;
  2276. if (alertController.preferredStyle == SPAlertControllerStyleAlert) {
  2277. [self offSetCenter:alertController];
  2278. }
  2279. [UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0.0 options:UIViewAnimationOptionCurveEaseOut animations:^{
  2280. CGRect controlViewFrame = alertController.view.frame;
  2281. if (alertController.preferredStyle == SPAlertControllerStyleActionSheet) {
  2282. controlViewFrame.origin.x = 0;
  2283. } else {
  2284. controlViewFrame.origin.x = (SP_SCREEN_WIDTH-controlViewFrame.size.width) / 2.0;
  2285. }
  2286. alertController.view.frame = controlViewFrame;
  2287. } completion:^(BOOL finished) {
  2288. [transitionContext completeTransition:finished];
  2289. [alertController layoutAlertControllerView];
  2290. }];
  2291. }
  2292. // 从左边弹出对应的dismiss动画
  2293. - (void)dismissCorrespondingFromLeftForController:(SPAlertController *)alertController transition:(id<UIViewControllerContextTransitioning>)transitionContext {
  2294. [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
  2295. CGRect controlViewFrame = alertController.view.frame;
  2296. controlViewFrame.origin.x = -controlViewFrame.size.width;
  2297. alertController.view.frame = controlViewFrame;
  2298. } completion:^(BOOL finished) {
  2299. [transitionContext completeTransition:finished];
  2300. }];
  2301. }
  2302. // 从顶部弹出的present动画
  2303. - (void)dropDownWhenPresentForController:(SPAlertController *)alertController transition:(id<UIViewControllerContextTransitioning>)transitionContext {
  2304. UIView *containerView = [transitionContext containerView];
  2305. // 将alertController的view添加到containerView上
  2306. [containerView addSubview:alertController.view];
  2307. // 标记需要刷新布局
  2308. [containerView setNeedsLayout];
  2309. // 在有标记刷新布局的情况下立即布局,这行代码很重要,第一:立即布局会立即调用SPAlertController的viewWillLayoutSubviews的方法,第二:立即布局后可以获取到alertController.view的frame
  2310. [containerView layoutIfNeeded];
  2311. // 这3行代码不能放在[containerView layoutIfNeeded]之前,如果放在之前,[containerView layoutIfNeeded]强制布局后会将以下设置的frame覆盖
  2312. CGRect controlViewFrame = alertController.view.frame;
  2313. controlViewFrame.origin.y = -controlViewFrame.size.height;
  2314. alertController.view.frame = controlViewFrame;
  2315. [UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0.0 options:UIViewAnimationOptionCurveEaseOut animations:^{
  2316. CGRect controlViewFrame = alertController.view.frame;
  2317. if (alertController.preferredStyle == SPAlertControllerStyleActionSheet) {
  2318. controlViewFrame.origin.y = 0;
  2319. } else {
  2320. controlViewFrame.origin.y = (SP_SCREEN_HEIGHT-controlViewFrame.size.height) / 2.0;
  2321. [self offSetCenter:alertController];
  2322. }
  2323. alertController.view.frame = controlViewFrame;
  2324. } completion:^(BOOL finished) {
  2325. [transitionContext completeTransition:finished];
  2326. [alertController layoutAlertControllerView];
  2327. }];
  2328. }
  2329. // 从顶部弹出对应的dismiss动画
  2330. - (void)dismissCorrespondingDropDownForController:(SPAlertController *)alertController transition:(id<UIViewControllerContextTransitioning>)transitionContext {
  2331. [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
  2332. CGRect controlViewFrame = alertController.view.frame;
  2333. controlViewFrame.origin.y = -controlViewFrame.size.height;
  2334. alertController.view.frame = controlViewFrame;
  2335. } completion:^(BOOL finished) {
  2336. [transitionContext completeTransition:finished];
  2337. }];
  2338. }
  2339. // alpha值从0到1变化的present动画
  2340. - (void)alphaWhenPresentForController:(SPAlertController *)alertController transition:(id<UIViewControllerContextTransitioning>)transitionContext {
  2341. UIView *containerView = [transitionContext containerView];
  2342. [containerView addSubview:alertController.view];
  2343. // 标记需要刷新布局
  2344. [containerView setNeedsLayout];
  2345. // 在有标记刷新布局的情况下立即布局,这行代码很重要,第一:立即布局会立即调用SPAlertController的viewWillLayoutSubviews的方法,第二:立即布局后可以获取到alertController.view的frame,不仅如此,走了viewWillLayoutSubviews键盘就会弹出,此后可以获取到alertController.offset
  2346. [containerView layoutIfNeeded];
  2347. alertController.view.alpha = 0;
  2348. [UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0.0 options:UIViewAnimationOptionCurveLinear animations:^{
  2349. [self offSetCenter:alertController];
  2350. alertController.view.alpha = 1;
  2351. } completion:^(BOOL finished) {
  2352. [transitionContext completeTransition:finished];
  2353. [alertController layoutAlertControllerView];
  2354. }];
  2355. }
  2356. // alpha值从0到1变化对应的的dismiss动画
  2357. - (void)dismissCorrespondingAlphaForController:(SPAlertController *)alertController transition:(id<UIViewControllerContextTransitioning>)transitionContext {
  2358. [UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0.0 options:UIViewAnimationOptionCurveLinear animations:^{
  2359. alertController.view.alpha = 0;
  2360. } completion:^(BOOL finished) {
  2361. [transitionContext completeTransition:finished];
  2362. }];
  2363. }
  2364. // 发散的prensent动画
  2365. - (void)expandWhenPresentForController:(SPAlertController *)alertController transition:(id<UIViewControllerContextTransitioning>)transitionContext {
  2366. UIView *containerView = [transitionContext containerView];
  2367. [containerView addSubview:alertController.view];
  2368. // 标记需要刷新布局
  2369. [containerView setNeedsLayout];
  2370. // 在有标记刷新布局的情况下立即布局,这行代码很重要,第一:立即布局会立即调用SPAlertController的viewWillLayoutSubviews的方法,第二:立即布局后可以获取到alertController.view的frame,不仅如此,走了viewWillLayoutSubviews键盘就会弹出,此后可以获取到alertController.offset
  2371. [containerView layoutIfNeeded];
  2372. alertController.view.transform = CGAffineTransformMakeScale(0.9, 0.9);
  2373. alertController.view.alpha = 0.0;
  2374. [UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0.0 options:UIViewAnimationOptionCurveLinear animations:^{
  2375. [self offSetCenter:alertController];
  2376. alertController.view.transform = CGAffineTransformIdentity;
  2377. alertController.view.alpha = 1.0;
  2378. } completion:^(BOOL finished) {
  2379. [transitionContext completeTransition:finished];
  2380. [alertController layoutAlertControllerView];
  2381. }];
  2382. }
  2383. // 发散对应的dismiss动画
  2384. - (void)dismissCorrespondingExpandForController:(SPAlertController *)alertController transition:(id<UIViewControllerContextTransitioning>)transitionContext {
  2385. [UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0.0 options:UIViewAnimationOptionCurveLinear animations:^{
  2386. alertController.view.transform = CGAffineTransformIdentity;
  2387. alertController.view.alpha = 0.0;
  2388. } completion:^(BOOL finished) {
  2389. [transitionContext completeTransition:finished];
  2390. }];
  2391. }
  2392. // 收缩的present动画
  2393. - (void)shrinkWhenPresentForController:(SPAlertController *)alertController transition:(id<UIViewControllerContextTransitioning>)transitionContext {
  2394. UIView *containerView = [transitionContext containerView];
  2395. [containerView addSubview:alertController.view];
  2396. // 标记需要刷新布局
  2397. [containerView setNeedsLayout];
  2398. // 在有标记刷新布局的情况下立即布局,这行代码很重要,第一:立即布局会立即调用SPAlertController的viewWillLayoutSubviews的方法,第二:立即布局后可以获取到alertController.view的frame,不仅如此,走了viewWillLayoutSubviews键盘就会弹出,此后可以获取到alertController.offset
  2399. [containerView layoutIfNeeded];
  2400. alertController.view.transform = CGAffineTransformMakeScale(1.1, 1.1);
  2401. alertController.view.alpha = 0;
  2402. [UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0.0 options:UIViewAnimationOptionCurveLinear animations:^{
  2403. [self offSetCenter:alertController];
  2404. alertController.view.transform = CGAffineTransformIdentity;
  2405. alertController.view.alpha = 1.0;
  2406. } completion:^(BOOL finished) {
  2407. [transitionContext completeTransition:finished];
  2408. [alertController layoutAlertControllerView];
  2409. }];
  2410. }
  2411. // 收缩对应的的dismiss动画
  2412. - (void)dismissCorrespondingShrinkForController:(SPAlertController *)alertController transition:(id<UIViewControllerContextTransitioning>)transitionContext {
  2413. // 与发散对应的dismiss动画相同
  2414. [self dismissCorrespondingExpandForController:alertController transition:transitionContext];
  2415. }
  2416. // 无动画
  2417. - (void)noneWhenPresentForController:(SPAlertController *)alertController transition:(id<UIViewControllerContextTransitioning>)transitionContext {
  2418. UIView *containerView = [transitionContext containerView];
  2419. [containerView addSubview:alertController.view];
  2420. [transitionContext completeTransition:transitionContext.animated];
  2421. }
  2422. - (void)dismissCorrespondingNoneForController:(SPAlertController *)alertController transition:(id<UIViewControllerContextTransitioning>)transitionContext {
  2423. [transitionContext completeTransition:transitionContext.animated];
  2424. }
  2425. - (void)offSetCenter:(SPAlertController *)alertController {
  2426. if (!CGPointEqualToPoint(alertController.offsetForAlert, CGPointZero)) {
  2427. CGPoint controlViewCenter = alertController.view.center;
  2428. controlViewCenter.x = SP_SCREEN_WIDTH / 2.0 + alertController.offsetForAlert.x;
  2429. controlViewCenter.y = SP_SCREEN_HEIGHT / 2.0 + alertController.offsetForAlert.y;
  2430. alertController.view.center = controlViewCenter;
  2431. }
  2432. }
  2433. @end
  2434. #pragma clang diagnostic pop
  2435. #pragma mark ---------------------------- SPAlertAnimation end --------------------------------