You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

432 lines
12 KiB

6 years ago
  1. //
  2. // FXPageControl.m
  3. //
  4. // Version 1.4
  5. //
  6. // Created by Nick Lockwood on 07/01/2010.
  7. // Copyright 2010 Charcoal Design
  8. //
  9. // Distributed under the permissive zlib License
  10. // Get the latest version of FXPageControl from here:
  11. //
  12. // https://github.com/nicklockwood/FXPageControl
  13. //
  14. // This software is provided 'as-is', without any express or implied
  15. // warranty. In no event will the authors be held liable for any damages
  16. // arising from the use of this software.
  17. //
  18. // Permission is granted to anyone to use this software for any purpose,
  19. // including commercial applications, and to alter it and redistribute it
  20. // freely, subject to the following restrictions:
  21. //
  22. // 1. The origin of this software must not be misrepresented; you must not
  23. // claim that you wrote the original software. If you use this software
  24. // in a product, an acknowledgment in the product documentation would be
  25. // appreciated but is not required.
  26. //
  27. // 2. Altered source versions must be plainly marked as such, and must not be
  28. // misrepresented as being the original software.
  29. //
  30. // 3. This notice may not be removed or altered from any source distribution.
  31. //
  32. #import "FXPageControl.h"
  33. #pragma GCC diagnostic ignored "-Wgnu"
  34. #pragma GCC diagnostic ignored "-Warc-repeated-use-of-weak"
  35. #pragma GCC diagnostic ignored "-Wdirect-ivar-access"
  36. #import <Availability.h>
  37. #if !__has_feature(objc_arc)
  38. #error This class requires automatic reference counting
  39. #endif
  40. const CGPathRef FXPageControlDotShapeCircle = (const CGPathRef)1;
  41. const CGPathRef FXPageControlDotShapeSquare = (const CGPathRef)2;
  42. const CGPathRef FXPageControlDotShapeTriangle = (const CGPathRef)3;
  43. #define LAST_SHAPE FXPageControlDotShapeTriangle
  44. @implementation NSObject (FXPageControl)
  45. - (UIImage *)pageControl:(__unused FXPageControl *)pageControl imageForDotAtIndex:(__unused NSInteger)index { return nil; }
  46. - (CGPathRef)pageControl:(__unused FXPageControl *)pageControl shapeForDotAtIndex:(__unused NSInteger)index { return NULL; }
  47. - (UIColor *)pageControl:(__unused FXPageControl *)pageControl colorForDotAtIndex:(__unused NSInteger)index { return nil; }
  48. - (UIImage *)pageControl:(__unused FXPageControl *)pageControl selectedImageForDotAtIndex:(__unused NSInteger)index { return nil; }
  49. - (CGPathRef)pageControl:(__unused FXPageControl *)pageControl selectedShapeForDotAtIndex:(__unused NSInteger)index { return NULL; }
  50. - (UIColor *)pageControl:(__unused FXPageControl *)pageControl selectedColorForDotAtIndex:(__unused NSInteger)index { return nil; }
  51. @end
  52. @implementation FXPageControl
  53. - (void)setUp
  54. {
  55. //needs redrawing if bounds change
  56. self.contentMode = UIViewContentModeRedraw;
  57. //set defaults
  58. _dotSpacing = 10.0f;
  59. _dotSize = 6.0f;
  60. _dotShadowOffset = CGSizeMake(0, 1);
  61. _selectedDotShadowOffset = CGSizeMake(0, 1);
  62. }
  63. - (id)initWithFrame:(CGRect)frame
  64. {
  65. if ((self = [super initWithFrame:frame]))
  66. {
  67. [self setUp];
  68. }
  69. return self;
  70. }
  71. - (id)initWithCoder:(NSCoder *)aDecoder
  72. {
  73. if ((self = [super initWithCoder:aDecoder]))
  74. {
  75. [self setUp];
  76. }
  77. return self;
  78. }
  79. - (void)dealloc
  80. {
  81. if (_dotShape > LAST_SHAPE) CGPathRelease(_dotShape);
  82. if (_selectedDotShape > LAST_SHAPE) CGPathRelease(_selectedDotShape);
  83. }
  84. - (CGSize)sizeForNumberOfPages:(__unused NSInteger)pageCount
  85. {
  86. CGFloat width = _dotSize + (_dotSize + _dotSpacing) * (_numberOfPages - 1);
  87. return _vertical? CGSizeMake(_dotSize, width): CGSizeMake(width, _dotSize);
  88. }
  89. - (void)updateCurrentPageDisplay
  90. {
  91. [self setNeedsDisplay];
  92. }
  93. - (void)drawRect:(__unused CGRect)rect
  94. {
  95. if (_numberOfPages > 1 || !_hidesForSinglePage)
  96. {
  97. CGContextRef context = UIGraphicsGetCurrentContext();
  98. CGSize size = [self sizeForNumberOfPages:_numberOfPages];
  99. if (_vertical)
  100. {
  101. CGContextTranslateCTM(context, self.frame.size.width / 2, (self.frame.size.height - size.height) / 2);
  102. }
  103. else
  104. {
  105. CGContextTranslateCTM(context, (self.frame.size.width - size.width) / 2, self.frame.size.height / 2);
  106. }
  107. for (int i = 0; i < _numberOfPages; i++)
  108. {
  109. UIImage *dotImage = nil;
  110. UIColor *dotColor = nil;
  111. CGPathRef dotShape = NULL;
  112. CGFloat dotSize = 0;
  113. UIColor *dotShadowColor = nil;
  114. CGSize dotShadowOffset = CGSizeZero;
  115. CGFloat dotShadowBlur = 0;
  116. if (i == _currentPage)
  117. {
  118. [_selectedDotColor setFill];
  119. dotImage = [_delegate pageControl:self selectedImageForDotAtIndex:i] ?: _selectedDotImage;
  120. dotShape = [_delegate pageControl:self selectedShapeForDotAtIndex:i] ?: _selectedDotShape ?: _dotShape;
  121. dotColor = [_delegate pageControl:self selectedColorForDotAtIndex:i] ?: _selectedDotColor ?: [UIColor blackColor];
  122. dotShadowBlur = _selectedDotShadowBlur;
  123. dotShadowColor = _selectedDotShadowColor;
  124. dotShadowOffset = _selectedDotShadowOffset;
  125. dotSize = _selectedDotSize ?: _dotSize;
  126. }
  127. else
  128. {
  129. [_dotColor setFill];
  130. dotImage = [_delegate pageControl:self imageForDotAtIndex:i] ?: _dotImage;
  131. dotShape = [_delegate pageControl:self shapeForDotAtIndex:i] ?: _dotShape;
  132. dotColor = [_delegate pageControl:self colorForDotAtIndex:i] ?: _dotColor;
  133. if (!dotColor)
  134. {
  135. //fall back to selected dot color with reduced alpha
  136. dotColor = [_delegate pageControl:self selectedColorForDotAtIndex:i] ?: _selectedDotColor ?: [UIColor blackColor];
  137. dotColor = [dotColor colorWithAlphaComponent:0.25f];
  138. }
  139. dotShadowBlur = _dotShadowBlur;
  140. dotShadowColor = _dotShadowColor;
  141. dotShadowOffset = _dotShadowOffset;
  142. dotSize = _dotSize;
  143. }
  144. CGContextSaveGState(context);
  145. CGFloat offset = (_dotSize + _dotSpacing) * i + _dotSize / 2;
  146. CGContextTranslateCTM(context, _vertical? 0: offset, _vertical? offset: 0);
  147. if (dotShadowColor && ![dotShadowColor isEqual:[UIColor clearColor]])
  148. {
  149. CGContextSetShadowWithColor(context, dotShadowOffset, dotShadowBlur, dotShadowColor.CGColor);
  150. }
  151. if (dotImage)
  152. {
  153. [dotImage drawInRect:CGRectMake(-dotImage.size.width / 2, -dotImage.size.height / 2, dotImage.size.width, dotImage.size.height)];
  154. }
  155. else
  156. {
  157. [dotColor setFill];
  158. if (!dotShape || dotShape == FXPageControlDotShapeCircle)
  159. {
  160. CGContextFillEllipseInRect(context, CGRectMake(-dotSize / 2, -dotSize / 2, dotSize, dotSize));
  161. }
  162. else if (dotShape == FXPageControlDotShapeSquare)
  163. {
  164. CGContextFillRect(context, CGRectMake(-dotSize / 2, -dotSize / 2, dotSize, dotSize));
  165. }
  166. else if (dotShape == FXPageControlDotShapeTriangle)
  167. {
  168. CGContextBeginPath(context);
  169. CGContextMoveToPoint(context, 0, -dotSize / 2);
  170. CGContextAddLineToPoint(context, dotSize / 2, dotSize / 2);
  171. CGContextAddLineToPoint(context, -dotSize / 2, dotSize / 2);
  172. CGContextAddLineToPoint(context, 0, -dotSize / 2);
  173. CGContextFillPath(context);
  174. }
  175. else
  176. {
  177. CGContextBeginPath(context);
  178. CGContextAddPath(context, dotShape);
  179. CGContextFillPath(context);
  180. }
  181. }
  182. CGContextRestoreGState(context);
  183. }
  184. }
  185. }
  186. - (NSInteger)clampedPageValue:(NSInteger)page
  187. {
  188. if (_wrapEnabled)
  189. {
  190. return _numberOfPages? (page + _numberOfPages) % _numberOfPages: 0;
  191. }
  192. else
  193. {
  194. return MIN(MAX(0, page), _numberOfPages - 1);
  195. }
  196. }
  197. - (void)setDotImage:(UIImage *)dotImage
  198. {
  199. if (_dotImage != dotImage)
  200. {
  201. _dotImage = dotImage;
  202. [self setNeedsDisplay];
  203. }
  204. }
  205. - (void)setDotShape:(CGPathRef)dotShape
  206. {
  207. if (_dotShape != dotShape)
  208. {
  209. if (_dotShape > LAST_SHAPE) CGPathRelease(_dotShape);
  210. _dotShape = dotShape;
  211. if (_dotShape > LAST_SHAPE) CGPathRetain(_dotShape);
  212. [self setNeedsDisplay];
  213. }
  214. }
  215. - (void)setDotSize:(CGFloat)dotSize
  216. {
  217. if (ABS(_dotSize - dotSize) > 0.001)
  218. {
  219. _dotSize = dotSize;
  220. [self setNeedsDisplay];
  221. }
  222. }
  223. - (void)setDotColor:(UIColor *)dotColor
  224. {
  225. if (_dotColor != dotColor)
  226. {
  227. _dotColor = dotColor;
  228. [self setNeedsDisplay];
  229. }
  230. }
  231. - (void)setDotShadowColor:(UIColor *)dotColor
  232. {
  233. if (_dotShadowColor != dotColor)
  234. {
  235. _dotShadowColor = dotColor;
  236. [self setNeedsDisplay];
  237. }
  238. }
  239. - (void)setDotShadowBlur:(CGFloat)dotShadowBlur
  240. {
  241. if (ABS(_dotShadowBlur - dotShadowBlur) > 0.001)
  242. {
  243. _dotShadowBlur = dotShadowBlur;
  244. [self setNeedsDisplay];
  245. }
  246. }
  247. - (void)setDotShadowOffset:(CGSize)dotShadowOffset
  248. {
  249. if (!CGSizeEqualToSize(_dotShadowOffset, dotShadowOffset))
  250. {
  251. _dotShadowOffset = dotShadowOffset;
  252. [self setNeedsDisplay];
  253. }
  254. }
  255. - (void)setSelectedDotImage:(UIImage *)dotImage
  256. {
  257. if (_selectedDotImage != dotImage)
  258. {
  259. _selectedDotImage = dotImage;
  260. [self setNeedsDisplay];
  261. }
  262. }
  263. - (void)setSelectedDotColor:(UIColor *)dotColor
  264. {
  265. if (_selectedDotColor != dotColor)
  266. {
  267. _selectedDotColor = dotColor;
  268. [self setNeedsDisplay];
  269. }
  270. }
  271. - (void)setSelectedDotShape:(CGPathRef)dotShape
  272. {
  273. if (_selectedDotShape != dotShape)
  274. {
  275. if (_selectedDotShape > LAST_SHAPE) CGPathRelease(_selectedDotShape);
  276. _selectedDotShape = dotShape;
  277. if (_selectedDotShape > LAST_SHAPE) CGPathRetain(_selectedDotShape);
  278. [self setNeedsDisplay];
  279. }
  280. }
  281. - (void)setSelectedDotSize:(CGFloat)dotSize
  282. {
  283. if (ABS(_selectedDotSize - dotSize) > 0.001)
  284. {
  285. _selectedDotSize = dotSize;
  286. [self setNeedsDisplay];
  287. }
  288. }
  289. - (void)setSelectedDotShadowColor:(UIColor *)dotColor
  290. {
  291. if (_selectedDotShadowColor != dotColor)
  292. {
  293. _selectedDotShadowColor = dotColor;
  294. [self setNeedsDisplay];
  295. }
  296. }
  297. - (void)setSelectedDotShadowBlur:(CGFloat)dotShadowBlur
  298. {
  299. if (ABS(_selectedDotShadowBlur - dotShadowBlur) > 0.001)
  300. {
  301. _selectedDotShadowBlur = dotShadowBlur;
  302. [self setNeedsDisplay];
  303. }
  304. }
  305. - (void)setSelectedDotShadowOffset:(CGSize)dotShadowOffset
  306. {
  307. if (!CGSizeEqualToSize(_selectedDotShadowOffset, dotShadowOffset))
  308. {
  309. _selectedDotShadowOffset = dotShadowOffset;
  310. [self setNeedsDisplay];
  311. }
  312. }
  313. - (void)setDotSpacing:(CGFloat)dotSpacing
  314. {
  315. if (ABS(_dotSpacing - dotSpacing) > 0.001)
  316. {
  317. _dotSpacing = dotSpacing;
  318. [self setNeedsDisplay];
  319. }
  320. }
  321. - (void)setDelegate:(id<FXPageControlDelegate>)delegate
  322. {
  323. if (_delegate != delegate)
  324. {
  325. _delegate = delegate;
  326. [self setNeedsDisplay];
  327. }
  328. }
  329. - (void)setCurrentPage:(NSInteger)page
  330. {
  331. _currentPage = [self clampedPageValue:page];
  332. [self setNeedsDisplay];
  333. }
  334. - (void)setNumberOfPages:(NSInteger)pages
  335. {
  336. if (_numberOfPages != pages)
  337. {
  338. _numberOfPages = pages;
  339. if (_currentPage >= pages)
  340. {
  341. _currentPage = pages - 1;
  342. }
  343. [self setNeedsDisplay];
  344. }
  345. }
  346. - (void)endTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event
  347. {
  348. CGPoint point = [touch locationInView:self];
  349. BOOL forward = _vertical? (point.y > self.frame.size.height / 2): (point.x > self.frame.size.width / 2);
  350. _currentPage = [self clampedPageValue:_currentPage + (forward? 1: -1)];
  351. if (!_defersCurrentPageDisplay)
  352. {
  353. [self setNeedsDisplay];
  354. }
  355. [self sendActionsForControlEvents:UIControlEventValueChanged];
  356. [super endTrackingWithTouch:touch withEvent:event];
  357. }
  358. - (CGSize)sizeThatFits:(__unused CGSize)size
  359. {
  360. CGSize dotSize = [self sizeForNumberOfPages:_numberOfPages];
  361. if (_selectedDotSize)
  362. {
  363. CGFloat width = (_selectedDotSize - _dotSize);
  364. CGFloat height = MAX(36, MAX(_dotSize, _selectedDotSize));
  365. dotSize.width = _vertical? height: dotSize.width + width;
  366. dotSize.height = _vertical? dotSize.height + width: height;
  367. }
  368. if ((_dotShadowColor && ![_dotShadowColor isEqual:[UIColor clearColor]]) ||
  369. (_selectedDotShadowColor && ![_selectedDotShadowColor isEqual:[UIColor clearColor]]))
  370. {
  371. dotSize.width += MAX(_dotShadowOffset.width, _selectedDotShadowOffset.width) * 2;
  372. dotSize.height += MAX(_dotShadowOffset.height, _selectedDotShadowOffset.height) * 2;
  373. dotSize.width += MAX(_dotShadowBlur, _selectedDotShadowBlur) * 2;
  374. dotSize.height += MAX(_dotShadowBlur, _selectedDotShadowBlur) * 2;
  375. }
  376. return dotSize;
  377. }
  378. - (CGSize)intrinsicContentSize
  379. {
  380. return [self sizeThatFits:self.bounds.size];
  381. }
  382. @end