PageRenderTime 46ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/Source/NSArray+PureLayout.m

https://gitlab.com/lisit1003/PureLayout
Objective C | 506 lines | 318 code | 33 blank | 155 comment | 53 complexity | 8718cfeab287f7c8e4ba0cc39c350ddf MD5 | raw file
  1. //
  2. // NSArray+PureLayout.m
  3. // v2.0.4
  4. // https://github.com/smileyborg/PureLayout
  5. //
  6. // Copyright (c) 2012 Richard Turton
  7. // Copyright (c) 2013-2014 Tyler Fox
  8. //
  9. // This code is distributed under the terms and conditions of the MIT license.
  10. //
  11. // Permission is hereby granted, free of charge, to any person obtaining a copy
  12. // of this software and associated documentation files (the "Software"), to
  13. // deal in the Software without restriction, including without limitation the
  14. // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  15. // sell copies of the Software, and to permit persons to whom the Software is
  16. // furnished to do so, subject to the following conditions:
  17. //
  18. // The above copyright notice and this permission notice shall be included in
  19. // all copies or substantial portions of the Software.
  20. //
  21. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  22. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  23. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  24. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  25. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  26. // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
  27. // IN THE SOFTWARE.
  28. //
  29. #import "NSArray+PureLayout.h"
  30. #import "ALView+PureLayout.h"
  31. #import "NSLayoutConstraint+PureLayout.h"
  32. #import "PureLayout+Internal.h"
  33. #pragma mark - NSArray+PureLayout
  34. @implementation NSArray (PureLayout)
  35. #pragma mark Array of Constraints
  36. /**
  37. Activates the constraints in this array.
  38. */
  39. - (void)autoInstallConstraints
  40. {
  41. #if __PureLayout_MinBaseSDK_iOS_8_0 || __PureLayout_MinBaseSDK_OSX_10_10
  42. if ([NSLayoutConstraint respondsToSelector:@selector(activateConstraints:)]) {
  43. for (id object in self) {
  44. if ([object isKindOfClass:[NSLayoutConstraint class]]) {
  45. [ALView al_applyGlobalStateToConstraint:object];
  46. }
  47. }
  48. if ([ALView al_preventAutomaticConstraintInstallation]) {
  49. [[ALView al_currentArrayOfCreatedConstraints] addObjectsFromArray:self];
  50. } else {
  51. [NSLayoutConstraint activateConstraints:self];
  52. }
  53. return;
  54. }
  55. #endif /* __PureLayout_MinBaseSDK_iOS_8_0 || __PureLayout_MinBaseSDK_OSX_10_10 */
  56. for (id object in self) {
  57. if ([object isKindOfClass:[NSLayoutConstraint class]]) {
  58. [((NSLayoutConstraint *)object) autoInstall];
  59. }
  60. }
  61. }
  62. /**
  63. Deactivates the constraints in this array.
  64. */
  65. - (void)autoRemoveConstraints
  66. {
  67. #if __PureLayout_MinBaseSDK_iOS_8_0 || __PureLayout_MinBaseSDK_OSX_10_10
  68. if ([NSLayoutConstraint respondsToSelector:@selector(deactivateConstraints:)]) {
  69. [NSLayoutConstraint deactivateConstraints:self];
  70. return;
  71. }
  72. #endif /* __PureLayout_MinBaseSDK_iOS_8_0 || __PureLayout_MinBaseSDK_OSX_10_10 */
  73. for (id object in self) {
  74. if ([object isKindOfClass:[NSLayoutConstraint class]]) {
  75. [((NSLayoutConstraint *)object) autoRemove];
  76. }
  77. }
  78. }
  79. #if __PureLayout_MinBaseSDK_iOS_8_0
  80. /**
  81. Sets the string as the identifier for the constraints in this array. Available in iOS 7.0 and OS X 10.9 and later.
  82. The identifier will be printed along with each constraint's description.
  83. This is helpful to document the constraints' purpose and aid in debugging.
  84. @param identifier A string used to identify the constraints in this array.
  85. @return This array.
  86. */
  87. - (instancetype)autoIdentifyConstraints:(NSString *)identifier
  88. {
  89. for (id object in self) {
  90. if ([object isKindOfClass:[NSLayoutConstraint class]]) {
  91. [((NSLayoutConstraint *)object) autoIdentify:identifier];
  92. }
  93. }
  94. return self;
  95. }
  96. #endif /* __PureLayout_MinBaseSDK_iOS_8_0 */
  97. #pragma mark Array of Views
  98. /**
  99. Aligns views in this array to one another along a given edge.
  100. Note: This array must contain at least 2 views, and all views must share a common superview.
  101. @param edge The edge to which the views will be aligned.
  102. @return An array of constraints added.
  103. */
  104. - (NSArray *)autoAlignViewsToEdge:(ALEdge)edge
  105. {
  106. NSAssert([self al_containsMinimumNumberOfViews:2], @"This array must contain at least 2 views.");
  107. NSMutableArray *constraints = [NSMutableArray new];
  108. ALView *previousView = nil;
  109. for (id object in self) {
  110. if ([object isKindOfClass:[ALView class]]) {
  111. ALView *view = (ALView *)object;
  112. view.translatesAutoresizingMaskIntoConstraints = NO;
  113. if (previousView) {
  114. [constraints addObject:[view autoPinEdge:edge toEdge:edge ofView:previousView]];
  115. }
  116. previousView = view;
  117. }
  118. }
  119. return constraints;
  120. }
  121. /**
  122. Aligns views in this array to one another along a given axis.
  123. Note: This array must contain at least 2 views, and all views must share a common superview.
  124. @param axis The axis to which the views will be aligned.
  125. @return An array of constraints added.
  126. */
  127. - (NSArray *)autoAlignViewsToAxis:(ALAxis)axis
  128. {
  129. NSAssert([self al_containsMinimumNumberOfViews:2], @"This array must contain at least 2 views.");
  130. NSMutableArray *constraints = [NSMutableArray new];
  131. ALView *previousView = nil;
  132. for (id object in self) {
  133. if ([object isKindOfClass:[ALView class]]) {
  134. ALView *view = (ALView *)object;
  135. view.translatesAutoresizingMaskIntoConstraints = NO;
  136. if (previousView) {
  137. [constraints addObject:[view autoAlignAxis:axis toSameAxisOfView:previousView]];
  138. }
  139. previousView = view;
  140. }
  141. }
  142. return constraints;
  143. }
  144. /**
  145. Matches a given dimension of all the views in this array.
  146. Note: This array must contain at least 2 views, and all views must share a common superview.
  147. @param dimension The dimension to match for all of the views.
  148. @return An array of constraints added.
  149. */
  150. - (NSArray *)autoMatchViewsDimension:(ALDimension)dimension
  151. {
  152. NSAssert([self al_containsMinimumNumberOfViews:2], @"This array must contain at least 2 views.");
  153. NSMutableArray *constraints = [NSMutableArray new];
  154. ALView *previousView = nil;
  155. for (id object in self) {
  156. if ([object isKindOfClass:[ALView class]]) {
  157. ALView *view = (ALView *)object;
  158. view.translatesAutoresizingMaskIntoConstraints = NO;
  159. if (previousView) {
  160. [constraints addObject:[view autoMatchDimension:dimension toDimension:dimension ofView:previousView]];
  161. }
  162. previousView = view;
  163. }
  164. }
  165. return constraints;
  166. }
  167. /**
  168. Sets the given dimension of all the views in this array to a given size.
  169. Note: This array must contain at least 1 view.
  170. @param dimension The dimension of each of the views to set.
  171. @param size The size to set the given dimension of each view to.
  172. @return An array of constraints added.
  173. */
  174. - (NSArray *)autoSetViewsDimension:(ALDimension)dimension toSize:(CGFloat)size
  175. {
  176. NSAssert([self al_containsMinimumNumberOfViews:1], @"This array must contain at least 1 view.");
  177. NSMutableArray *constraints = [NSMutableArray new];
  178. for (id object in self) {
  179. if ([object isKindOfClass:[ALView class]]) {
  180. ALView *view = (ALView *)object;
  181. view.translatesAutoresizingMaskIntoConstraints = NO;
  182. [constraints addObject:[view autoSetDimension:dimension toSize:size]];
  183. }
  184. }
  185. return constraints;
  186. }
  187. /**
  188. Sets all of the views in this array to a given size.
  189. Note: This array must contain at least 1 view.
  190. @param size The size to set each view's dimensions to.
  191. @return An array of constraints added.
  192. */
  193. - (NSArray *)autoSetViewsDimensionsToSize:(CGSize)size
  194. {
  195. NSMutableArray *constraints = [NSMutableArray new];
  196. [constraints addObjectsFromArray:[self autoSetViewsDimension:ALDimensionWidth toSize:size.width]];
  197. [constraints addObjectsFromArray:[self autoSetViewsDimension:ALDimensionHeight toSize:size.height]];
  198. return constraints;
  199. }
  200. /**
  201. Distributes the views in this array equally along the selected axis in their superview.
  202. Views will be the same size (variable) in the dimension along the axis and will have spacing (fixed) between them,
  203. including from the first and last views to their superview.
  204. @param axis The axis along which to distribute the views.
  205. @param alignment The attribute to use to align all the views to one another.
  206. @param spacing The fixed amount of spacing between each view.
  207. @return An array of constraints added.
  208. */
  209. - (NSArray *)autoDistributeViewsAlongAxis:(ALAxis)axis
  210. alignedTo:(ALAttribute)alignment
  211. withFixedSpacing:(CGFloat)spacing
  212. {
  213. return [self autoDistributeViewsAlongAxis:axis
  214. alignedTo:alignment
  215. withFixedSpacing:spacing
  216. insetSpacing:YES];
  217. }
  218. /**
  219. Distributes the views in this array equally along the selected axis in their superview.
  220. Views will be the same size (variable) in the dimension along the axis and will have spacing (fixed) between them.
  221. The first and last views can optionally be inset from their superview by the same amount of spacing as between views.
  222. @param axis The axis along which to distribute the views.
  223. @param alignment The attribute to use to align all the views to one another.
  224. @param spacing The fixed amount of spacing between each view.
  225. @param shouldSpaceInsets Whether the first and last views should be equally inset from their superview.
  226. @return An array of constraints added.
  227. */
  228. - (NSArray *)autoDistributeViewsAlongAxis:(ALAxis)axis
  229. alignedTo:(ALAttribute)alignment
  230. withFixedSpacing:(CGFloat)spacing
  231. insetSpacing:(BOOL)shouldSpaceInsets
  232. {
  233. return [self autoDistributeViewsAlongAxis:axis
  234. alignedTo:alignment
  235. withFixedSpacing:spacing
  236. insetSpacing:shouldSpaceInsets
  237. matchedSizes:YES];
  238. }
  239. /**
  240. Distributes the views in this array equally along the selected axis in their superview.
  241. Views will have fixed spacing between them, and can optionally be constrained to the same size in the dimension along the axis.
  242. The first and last views can optionally be inset from their superview by the same amount of spacing as between views.
  243. @param axis The axis along which to distribute the views.
  244. @param alignment The attribute to use to align all the views to one another.
  245. @param spacing The fixed amount of spacing between each view.
  246. @param shouldSpaceInsets Whether the first and last views should be equally inset from their superview.
  247. @param shouldMatchSizes Whether all views will be constrained to be the same size in the dimension along the axis.
  248. NOTE: All views must specify an intrinsic content size if passing NO, otherwise the layout will be ambiguous!
  249. @return An array of constraints added.
  250. */
  251. - (NSArray *)autoDistributeViewsAlongAxis:(ALAxis)axis
  252. alignedTo:(ALAttribute)alignment
  253. withFixedSpacing:(CGFloat)spacing
  254. insetSpacing:(BOOL)shouldSpaceInsets
  255. matchedSizes:(BOOL)shouldMatchSizes
  256. {
  257. NSAssert([self al_containsMinimumNumberOfViews:2], @"This array must contain at least 2 views to distribute.");
  258. ALDimension matchedDimension;
  259. ALEdge firstEdge, lastEdge;
  260. switch (axis) {
  261. case ALAxisHorizontal:
  262. case ALAxisBaseline: // same value as ALAxisLastBaseline
  263. #if __PureLayout_MinBaseSDK_iOS_8_0
  264. case ALAxisFirstBaseline:
  265. #endif /* __PureLayout_MinBaseSDK_iOS_8_0 */
  266. matchedDimension = ALDimensionWidth;
  267. firstEdge = ALEdgeLeading;
  268. lastEdge = ALEdgeTrailing;
  269. break;
  270. case ALAxisVertical:
  271. matchedDimension = ALDimensionHeight;
  272. firstEdge = ALEdgeTop;
  273. lastEdge = ALEdgeBottom;
  274. break;
  275. default:
  276. NSAssert(nil, @"Not a valid ALAxis.");
  277. return nil;
  278. }
  279. CGFloat leadingSpacing = shouldSpaceInsets ? spacing : 0.0;
  280. CGFloat trailingSpacing = shouldSpaceInsets ? spacing : 0.0;
  281. NSMutableArray *constraints = [NSMutableArray new];
  282. ALView *previousView = nil;
  283. for (id object in self) {
  284. if ([object isKindOfClass:[ALView class]]) {
  285. ALView *view = (ALView *)object;
  286. view.translatesAutoresizingMaskIntoConstraints = NO;
  287. if (previousView) {
  288. // Second, Third, ... View
  289. [constraints addObject:[view autoPinEdge:firstEdge toEdge:lastEdge ofView:previousView withOffset:spacing]];
  290. if (shouldMatchSizes) {
  291. [constraints addObject:[view autoMatchDimension:matchedDimension toDimension:matchedDimension ofView:previousView]];
  292. }
  293. [constraints addObject:[view al_alignAttribute:alignment toView:previousView forAxis:axis]];
  294. }
  295. else {
  296. // First view
  297. [constraints addObject:[view autoPinEdgeToSuperviewEdge:firstEdge withInset:leadingSpacing]];
  298. }
  299. previousView = view;
  300. }
  301. }
  302. if (previousView) {
  303. // Last View
  304. [constraints addObject:[previousView autoPinEdgeToSuperviewEdge:lastEdge withInset:trailingSpacing]];
  305. }
  306. return constraints;
  307. }
  308. /**
  309. Distributes the views in this array equally along the selected axis in their superview.
  310. Views will be the same size (fixed) in the dimension along the axis and will have spacing (variable) between them,
  311. including from the first and last views to their superview.
  312. @param axis The axis along which to distribute the views.
  313. @param alignment The attribute to use to align all the views to one another.
  314. @param size The fixed size of each view in the dimension along the given axis.
  315. @return An array of constraints added.
  316. */
  317. - (NSArray *)autoDistributeViewsAlongAxis:(ALAxis)axis
  318. alignedTo:(ALAttribute)alignment
  319. withFixedSize:(CGFloat)size
  320. {
  321. return [self autoDistributeViewsAlongAxis:axis
  322. alignedTo:alignment
  323. withFixedSize:size
  324. insetSpacing:YES];
  325. }
  326. /**
  327. Distributes the views in this array equally along the selected axis in their superview.
  328. Views will be the same size (fixed) in the dimension along the axis and will have spacing (variable) between them.
  329. The first and last views can optionally be inset from their superview by the same amount of spacing as between views.
  330. @param axis The axis along which to distribute the views.
  331. @param alignment The attribute to use to align all the views to one another.
  332. @param size The fixed size of each view in the dimension along the given axis.
  333. @param shouldSpaceInsets Whether the first and last views should be equally inset from their superview.
  334. @return An array of constraints added.
  335. */
  336. - (NSArray *)autoDistributeViewsAlongAxis:(ALAxis)axis
  337. alignedTo:(ALAttribute)alignment
  338. withFixedSize:(CGFloat)size
  339. insetSpacing:(BOOL)shouldSpaceInsets
  340. {
  341. NSAssert([self al_containsMinimumNumberOfViews:2], @"This array must contain at least 2 views to distribute.");
  342. ALDimension fixedDimension;
  343. NSLayoutAttribute attribute;
  344. switch (axis) {
  345. case ALAxisHorizontal:
  346. case ALAxisBaseline: // same value as ALAxisLastBaseline
  347. #if __PureLayout_MinBaseSDK_iOS_8_0
  348. case ALAxisFirstBaseline:
  349. #endif /* __PureLayout_MinBaseSDK_iOS_8_0 */
  350. fixedDimension = ALDimensionWidth;
  351. attribute = NSLayoutAttributeCenterX;
  352. break;
  353. case ALAxisVertical:
  354. fixedDimension = ALDimensionHeight;
  355. attribute = NSLayoutAttributeCenterY;
  356. break;
  357. default:
  358. NSAssert(nil, @"Not a valid ALAxis.");
  359. return nil;
  360. }
  361. #if TARGET_OS_IPHONE
  362. # if !defined(PURELAYOUT_APP_EXTENSIONS)
  363. BOOL isRightToLeftLayout = [[UIApplication sharedApplication] userInterfaceLayoutDirection] == UIUserInterfaceLayoutDirectionRightToLeft;
  364. # else
  365. // App Extensions may not access -[UIApplication sharedApplication]; fall back to checking the bundle's preferred localization character direction
  366. BOOL isRightToLeftLayout = [NSLocale characterDirectionForLanguage:[[NSBundle mainBundle] preferredLocalizations][0]] == NSLocaleLanguageDirectionRightToLeft;
  367. # endif /* !defined(PURELAYOUT_APP_EXTENSIONS) */
  368. #else
  369. BOOL isRightToLeftLayout = [[NSApplication sharedApplication] userInterfaceLayoutDirection] == NSUserInterfaceLayoutDirectionRightToLeft;
  370. #endif /* TARGET_OS_IPHONE */
  371. BOOL shouldFlipOrder = isRightToLeftLayout && (axis != ALAxisVertical); // imitate the effect of leading/trailing when distributing horizontally
  372. NSMutableArray *constraints = [NSMutableArray new];
  373. NSArray *views = [self al_copyViewsOnly];
  374. NSUInteger numberOfViews = [views count];
  375. ALView *commonSuperview = [views al_commonSuperviewOfViews];
  376. ALView *previousView = nil;
  377. for (NSUInteger i = 0; i < numberOfViews; i++) {
  378. ALView *view = shouldFlipOrder ? views[numberOfViews - i - 1] : views[i];
  379. view.translatesAutoresizingMaskIntoConstraints = NO;
  380. [constraints addObject:[view autoSetDimension:fixedDimension toSize:size]];
  381. CGFloat multiplier, constant;
  382. if (shouldSpaceInsets) {
  383. multiplier = (i * 2.0 + 2.0) / (numberOfViews + 1.0);
  384. constant = (multiplier - 1.0) * size / 2.0;
  385. } else {
  386. multiplier = (i * 2.0) / (numberOfViews - 1.0);
  387. constant = (-multiplier + 1.0) * size / 2.0;
  388. }
  389. // If the multiplier is very close to 0, set it to the minimum value to prevent the second item in the constraint from being lost. Filed as rdar://19168380
  390. if (fabs(multiplier) < kMULTIPLIER_MIN_VALUE) {
  391. multiplier = kMULTIPLIER_MIN_VALUE;
  392. }
  393. NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:view attribute:attribute relatedBy:NSLayoutRelationEqual toItem:commonSuperview attribute:attribute multiplier:multiplier constant:constant];
  394. [constraint autoInstall];
  395. [constraints addObject:constraint];
  396. if (previousView) {
  397. [constraints addObject:[view al_alignAttribute:alignment toView:previousView forAxis:axis]];
  398. }
  399. previousView = view;
  400. }
  401. return constraints;
  402. }
  403. #pragma mark Internal Helper Methods
  404. /**
  405. Returns the common superview for the views in this array.
  406. Raises an exception if the views in this array do not share a common superview.
  407. @return The common superview for the views in this array.
  408. */
  409. - (ALView *)al_commonSuperviewOfViews
  410. {
  411. ALView *commonSuperview = nil;
  412. ALView *previousView = nil;
  413. for (id object in self) {
  414. if ([object isKindOfClass:[ALView class]]) {
  415. ALView *view = (ALView *)object;
  416. if (previousView) {
  417. commonSuperview = [view al_commonSuperviewWithView:commonSuperview];
  418. } else {
  419. commonSuperview = view;
  420. }
  421. previousView = view;
  422. }
  423. }
  424. NSAssert(commonSuperview, @"Can't constrain views that do not share a common superview. Make sure that all the views in this array have been added into the same view hierarchy.");
  425. return commonSuperview;
  426. }
  427. /**
  428. Determines whether this array contains a minimum number of views.
  429. @param minimumNumberOfViews The minimum number of views to check for.
  430. @return YES if this array contains at least the minimum number of views, NO otherwise.
  431. */
  432. - (BOOL)al_containsMinimumNumberOfViews:(NSUInteger)minimumNumberOfViews
  433. {
  434. NSUInteger numberOfViews = 0;
  435. for (id object in self) {
  436. if ([object isKindOfClass:[ALView class]]) {
  437. numberOfViews++;
  438. if (numberOfViews >= minimumNumberOfViews) {
  439. return YES;
  440. }
  441. }
  442. }
  443. return numberOfViews >= minimumNumberOfViews;
  444. }
  445. /**
  446. Creates a copy of this array containing only the view objects in it.
  447. @return A new array containing only the views that are in this array.
  448. */
  449. - (NSArray *)al_copyViewsOnly
  450. {
  451. NSMutableArray *viewsOnlyArray = [NSMutableArray arrayWithCapacity:[self count]];
  452. for (id object in self) {
  453. if ([object isKindOfClass:[ALView class]]) {
  454. [viewsOnlyArray addObject:object];
  455. }
  456. }
  457. return viewsOnlyArray;
  458. }
  459. @end