GIDSignInButton.m 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641
  1. // Copyright 2021 Google LLC
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. #import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDSignInButton.h"
  15. #import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDSignIn.h"
  16. #import "GoogleSignIn/Sources/GIDScopes.h"
  17. #import "GoogleSignIn/Sources/GIDSignInInternalOptions.h"
  18. #import "GoogleSignIn/Sources/GIDSignInStrings.h"
  19. #import "GoogleSignIn/Sources/GIDSignIn_Private.h"
  20. #import "GoogleSignIn/Sources/NSBundle+GID3PAdditions.h"
  21. NS_ASSUME_NONNULL_BEGIN
  22. #pragma mark - Constants
  23. // Standard accessibility identifier.
  24. static NSString *const kAccessibilityIdentifier = @"GIDSignInButton";
  25. // The name of the font for button text.
  26. static NSString *const kFontNameRobotoBold = @"Roboto-Bold";
  27. // Button text font size.
  28. static const CGFloat kFontSize = 14;
  29. #pragma mark - Icon Constants
  30. // The name of the image for the Google "G"
  31. static NSString *const kGoogleImageName = @"google";
  32. // Keys used for NSCoding.
  33. static NSString *const kStyleKey = @"style";
  34. static NSString *const kColorSchemeKey = @"color_scheme";
  35. static NSString *const kButtonState = @"state";
  36. #pragma mark - Sizing Constants
  37. // The corner radius of the button
  38. static const int kCornerRadius = 2;
  39. // The standard height of the sign in button.
  40. static const int kButtonHeight = 48;
  41. // The width of the icon part of the button in points.
  42. static const int kIconWidth = 40;
  43. // Left and right text padding.
  44. static const int kTextPadding = 14;
  45. // The icon (UIImage)'s frame.
  46. static const CGRect kIconFrame = { {9, 10}, {29, 30} };
  47. #pragma mark - Appearance Constants
  48. static const CGFloat kBorderWidth = 4;
  49. static const CGFloat kHaloShadowAlpha = 12.0 / 100.0;
  50. static const CGFloat kHaloShadowBlur = 2;
  51. static const CGFloat kDropShadowAlpha = 24.0 / 100.0;
  52. static const CGFloat kDropShadowBlur = 2;
  53. static const CGFloat kDropShadowYOffset = 2;
  54. static const CGFloat kDisabledIconAlpha = 40.0 / 100.0;
  55. #pragma mark - Colors
  56. // All colors in hex RGBA format (0xRRGGBBAA)
  57. static const NSUInteger kColorGoogleBlue = 0x4285f4ff;
  58. static const NSUInteger kColorGoogleDarkBlue = 0x3367d6ff;
  59. static const NSUInteger kColorWhite = 0xffffffff;
  60. static const NSUInteger kColorLightestGrey = 0x00000014;
  61. static const NSUInteger kColorLightGrey = 0xeeeeeeff;
  62. static const NSUInteger kColorDisabledGrey = 0x00000066;
  63. static const NSUInteger kColorDarkestGrey = 0x00000089;
  64. static NSUInteger kColors[12] = {
  65. // |Background|, |Foreground|,
  66. kColorGoogleBlue, kColorWhite, // Dark Google Normal
  67. kColorLightestGrey, kColorDisabledGrey, // Dark Google Disabled
  68. kColorGoogleDarkBlue, kColorWhite, // Dark Google Pressed
  69. kColorWhite, kColorDarkestGrey, // Light Google Normal
  70. kColorLightestGrey, kColorDisabledGrey, // Light Google Disabled
  71. kColorLightGrey, kColorDarkestGrey, // Light Google Pressed
  72. };
  73. // The state of the button:
  74. typedef NS_ENUM(NSUInteger, GIDSignInButtonState) {
  75. kGIDSignInButtonStateNormal = 0,
  76. kGIDSignInButtonStateDisabled = 1,
  77. kGIDSignInButtonStatePressed = 2,
  78. };
  79. static NSUInteger const kNumGIDSignInButtonStates = 3;
  80. // Used to lookup specific colors from the kColors table:
  81. typedef NS_ENUM(NSUInteger, GIDSignInButtonStyleColor) {
  82. kGIDSignInButtonStyleColorBackground = 0,
  83. kGIDSignInButtonStyleColorForeground = 1,
  84. };
  85. static NSUInteger const kNumGIDSignInButtonStyleColors = 2;
  86. // This method just pulls the correct value out of the kColors table and returns it as a UIColor.
  87. static UIColor *colorForStyleState(GIDSignInButtonColorScheme style,
  88. GIDSignInButtonState state,
  89. GIDSignInButtonStyleColor color) {
  90. NSUInteger stateWidth = kNumGIDSignInButtonStyleColors;
  91. NSUInteger styleWidth = kNumGIDSignInButtonStates * stateWidth;
  92. NSUInteger index = (style * styleWidth) + (state * stateWidth) + color;
  93. NSUInteger colorValue = kColors[index];
  94. return [UIColor colorWithRed:(CGFloat)(((colorValue & 0xff000000) >> 24) / 255.0f) \
  95. green:(CGFloat)(((colorValue & 0x00ff0000) >> 16) / 255.0f) \
  96. blue:(CGFloat)(((colorValue & 0x0000ff00) >> 8) / 255.0f) \
  97. alpha:(CGFloat)(((colorValue & 0x000000ff) >> 0) / 255.0f)];
  98. }
  99. #pragma mark - UIImage Category Forward Declaration
  100. @interface UIImage (GIDAdditions_Private)
  101. - (UIImage *)gid_imageWithBlendMode:(CGBlendMode)blendMode color:(UIColor *)color;
  102. @end
  103. #pragma mark - GIDSignInButton Private Properties
  104. @interface GIDSignInButton ()
  105. // The state (normal, pressed, disabled) of the button.
  106. @property(nonatomic, assign) GIDSignInButtonState buttonState;
  107. @end
  108. #pragma mark -
  109. @implementation GIDSignInButton {
  110. UIImageView *_icon;
  111. }
  112. #pragma mark - Object lifecycle
  113. - (id)initWithFrame:(CGRect)frame {
  114. self = [super initWithFrame:frame];
  115. if (self) {
  116. [self sharedInit];
  117. }
  118. return self;
  119. }
  120. - (void)sharedInit {
  121. self.clipsToBounds = YES;
  122. self.backgroundColor = [UIColor clearColor];
  123. // Accessibility settings:
  124. self.isAccessibilityElement = YES;
  125. self.accessibilityTraits = UIAccessibilityTraitButton;
  126. self.accessibilityIdentifier = kAccessibilityIdentifier;
  127. // Default style settings.
  128. _style = kGIDSignInButtonStyleStandard;
  129. _colorScheme = kGIDSignInButtonColorSchemeLight;
  130. _buttonState = kGIDSignInButtonStateNormal;
  131. // Icon for branding image:
  132. _icon = [[UIImageView alloc] initWithFrame:kIconFrame];
  133. _icon.contentMode = UIViewContentModeCenter;
  134. _icon.userInteractionEnabled = NO;
  135. [self addSubview:_icon];
  136. // Load font for "Sign in with Google" text
  137. [NSBundle gid_registerFonts];
  138. // Setup normal/highlighted state transitions:
  139. [self addTarget:self
  140. action:@selector(setNeedsDisplay)
  141. forControlEvents:UIControlEventAllTouchEvents];
  142. [self addTarget:self
  143. action:@selector(switchToPressed)
  144. forControlEvents:UIControlEventTouchDown |
  145. UIControlEventTouchDragInside |
  146. UIControlEventTouchDragEnter];
  147. [self addTarget:self
  148. action:@selector(switchToNormal)
  149. forControlEvents:UIControlEventTouchDragExit |
  150. UIControlEventTouchDragOutside |
  151. UIControlEventTouchCancel |
  152. UIControlEventTouchUpInside];
  153. // Update the icon, etc.
  154. [self updateUI];
  155. }
  156. #pragma mark - NSCoding
  157. - (nullable instancetype)initWithCoder:(NSCoder *)aDecoder {
  158. self = [super initWithCoder:aDecoder];
  159. if (self) {
  160. [self sharedInit];
  161. if ([aDecoder containsValueForKey:kStyleKey]) {
  162. _style = [aDecoder decodeIntegerForKey:kStyleKey];
  163. }
  164. if ([aDecoder containsValueForKey:kColorSchemeKey]) {
  165. _colorScheme = [aDecoder decodeIntegerForKey:kColorSchemeKey];
  166. }
  167. if ([aDecoder containsValueForKey:kButtonState]) {
  168. _buttonState = [aDecoder decodeIntegerForKey:kButtonState];
  169. }
  170. }
  171. return self;
  172. }
  173. - (void)encodeWithCoder:(NSCoder *)aCoder {
  174. [super encodeWithCoder:aCoder];
  175. [aCoder encodeInteger:_style forKey:kStyleKey];
  176. [aCoder encodeInteger:_colorScheme forKey:kColorSchemeKey];
  177. [aCoder encodeInteger:_buttonState forKey:kButtonState];
  178. }
  179. #pragma mark - UI
  180. - (void)updateUI {
  181. // Reload the icon.
  182. [self loadIcon];
  183. // Set a useful accessibility label here even if we're not showing text.
  184. // Get localized button text from bundle.
  185. self.accessibilityLabel = [self buttonText];
  186. // Force constrain frame sizes:
  187. [self setFrame:self.frame];
  188. [self setNeedsUpdateConstraints];
  189. [self setNeedsDisplay];
  190. }
  191. - (void)loadIcon {
  192. NSString *resourceName = kGoogleImageName;
  193. NSBundle *gidBundle = [NSBundle gid_frameworkBundle];
  194. NSString *resourcePath = [gidBundle pathForResource:resourceName ofType:@"png"];
  195. UIImage *image = [UIImage imageWithContentsOfFile:resourcePath];
  196. if (_buttonState == kGIDSignInButtonStateDisabled) {
  197. _icon.image = [image gid_imageWithBlendMode:kCGBlendModeMultiply
  198. color:[UIColor colorWithWhite:0
  199. alpha:kDisabledIconAlpha]];
  200. } else {
  201. _icon.image = image;
  202. }
  203. }
  204. #pragma mark - State Transitions
  205. - (void)switchToPressed {
  206. [self setButtonState:kGIDSignInButtonStatePressed];
  207. }
  208. - (void)switchToNormal {
  209. [self setButtonState:kGIDSignInButtonStateNormal];
  210. }
  211. - (void)switchToDisabled {
  212. [self setButtonState:kGIDSignInButtonStateDisabled];
  213. }
  214. #pragma mark - Properties
  215. - (void)setStyle:(GIDSignInButtonStyle)style {
  216. if (style == _style) {
  217. return;
  218. }
  219. _style = style;
  220. [self updateUI];
  221. }
  222. - (void)setColorScheme:(GIDSignInButtonColorScheme)colorScheme {
  223. if (colorScheme == _colorScheme) {
  224. return;
  225. }
  226. _colorScheme = colorScheme;
  227. [self updateUI];
  228. }
  229. - (void)setEnabled:(BOOL)enabled {
  230. if (enabled == self.enabled) {
  231. return;
  232. }
  233. [super setEnabled:enabled];
  234. if (enabled) {
  235. [self switchToNormal];
  236. } else {
  237. [self switchToDisabled];
  238. }
  239. [self updateUI];
  240. }
  241. - (void)setButtonState:(GIDSignInButtonState)buttonState {
  242. if (buttonState == _buttonState) {
  243. return;
  244. }
  245. _buttonState = buttonState;
  246. [self setNeedsDisplay];
  247. }
  248. - (void)setFrame:(CGRect)frame {
  249. // Constrain the frame size to sizes we want.
  250. frame.size = [self sizeThatFits:frame.size];
  251. if (CGRectEqualToRect(frame, self.frame)) {
  252. return;
  253. }
  254. [super setFrame:frame];
  255. [self setNeedsUpdateConstraints];
  256. [self setNeedsDisplay];
  257. }
  258. #pragma mark - Helpers
  259. - (CGFloat)minWidth {
  260. if (_style == kGIDSignInButtonStyleIconOnly) {
  261. return kIconWidth + (kBorderWidth * 2);
  262. }
  263. NSString *text = [self buttonText];
  264. CGSize textSize = [[self class] textSize:text withFont:[[self class] buttonTextFont]];
  265. return ceil(kIconWidth + (kTextPadding * 2) + textSize.width + (kBorderWidth * 2));
  266. }
  267. - (BOOL)isConstraint:(NSLayoutConstraint *)constraintA
  268. equalToConstraint:(NSLayoutConstraint *)constraintB {
  269. return constraintA.priority == constraintB.priority &&
  270. constraintA.firstItem == constraintB.firstItem &&
  271. constraintA.firstAttribute == constraintB.firstAttribute &&
  272. constraintA.relation == constraintB.relation &&
  273. constraintA.secondItem == constraintB.secondItem &&
  274. constraintA.secondAttribute == constraintB.secondAttribute &&
  275. constraintA.multiplier == constraintB.multiplier &&
  276. constraintA.constant == constraintB.constant;
  277. }
  278. #pragma mark - Overrides
  279. - (CGSize)sizeThatFits:(CGSize)size {
  280. switch (_style) {
  281. case kGIDSignInButtonStyleIconOnly:
  282. return CGSizeMake([self minWidth], kButtonHeight);
  283. case kGIDSignInButtonStyleStandard:
  284. case kGIDSignInButtonStyleWide: {
  285. return CGSizeMake(MAX(size.width, [self minWidth]), kButtonHeight);
  286. }
  287. }
  288. }
  289. - (void)updateConstraints {
  290. NSLayoutRelation widthConstraintRelation;
  291. // For icon style, we want to ensure a fixed width
  292. if (_style == kGIDSignInButtonStyleIconOnly) {
  293. widthConstraintRelation = NSLayoutRelationEqual;
  294. } else {
  295. widthConstraintRelation = NSLayoutRelationGreaterThanOrEqual;
  296. }
  297. // Define a width constraint ensuring that we don't go below our minimum width
  298. NSLayoutConstraint *widthConstraint =
  299. [NSLayoutConstraint constraintWithItem:self
  300. attribute:NSLayoutAttributeWidth
  301. relatedBy:widthConstraintRelation
  302. toItem:nil
  303. attribute:NSLayoutAttributeNotAnAttribute
  304. multiplier:1.0
  305. constant:[self minWidth]];
  306. widthConstraint.identifier = @"buttonWidth - auto generated by GIDSignInButton";
  307. // Define a height constraint using our constant height
  308. NSLayoutConstraint *heightConstraint =
  309. [NSLayoutConstraint constraintWithItem:self
  310. attribute:NSLayoutAttributeHeight
  311. relatedBy:NSLayoutRelationEqual
  312. toItem:nil
  313. attribute:NSLayoutAttributeNotAnAttribute
  314. multiplier:1.0
  315. constant:kButtonHeight];
  316. heightConstraint.identifier = @"buttonHeight - auto generated by GIDSignInButton";
  317. // By default, add our width and height constraints
  318. BOOL addWidthConstraint = YES;
  319. BOOL addHeightConstraint = YES;
  320. for (NSLayoutConstraint *constraint in self.constraints) {
  321. // If it is equivalent to our width or height constraint, don't add ours later
  322. if ([self isConstraint:constraint equalToConstraint:widthConstraint]) {
  323. addWidthConstraint = NO;
  324. continue;
  325. }
  326. if ([self isConstraint:constraint equalToConstraint:heightConstraint]) {
  327. addHeightConstraint = NO;
  328. continue;
  329. }
  330. if (constraint.firstItem == self) {
  331. // If it is a height constraint of any relation, remove it
  332. if (constraint.firstAttribute == NSLayoutAttributeHeight) {
  333. [self removeConstraint:constraint];
  334. }
  335. // If it is a width constraint of any relation, remove it if it will conflict with ours
  336. if (constraint.firstAttribute == NSLayoutAttributeWidth &&
  337. (constraint.constant < [self minWidth] || _style == kGIDSignInButtonStyleIconOnly)) {
  338. [self removeConstraint:constraint];
  339. }
  340. }
  341. }
  342. if (addWidthConstraint) {
  343. [self addConstraint:widthConstraint];
  344. }
  345. if (addHeightConstraint) {
  346. [self addConstraint:heightConstraint];
  347. }
  348. [super updateConstraints];
  349. }
  350. #pragma mark - Rendering
  351. - (void)drawRect:(CGRect)rect {
  352. [super drawRect:rect];
  353. CGContextRef context = UIGraphicsGetCurrentContext();
  354. CGContextRetain(context);
  355. if (context == NULL) {
  356. return;
  357. }
  358. // Draw the button background
  359. [self drawButtonBackground:context];
  360. // Draw the text
  361. [self drawButtonText:context];
  362. CGContextRelease(context);
  363. context = NULL;
  364. }
  365. #pragma mark - Button Background Rendering
  366. - (void)drawButtonBackground:(CGContextRef)context {
  367. CGContextSaveGState(context);
  368. // Normalize the coordinate system of our graphics context
  369. // (0,0) -----> +x
  370. // |
  371. // |
  372. // \/ +y
  373. CGContextScaleCTM(context, 1, -1);
  374. CGContextTranslateCTM(context, 0, -self.bounds.size.height);
  375. // Get the colors for the current state and configuration
  376. UIColor *background = colorForStyleState(_colorScheme,
  377. _buttonState,
  378. kGIDSignInButtonStyleColorBackground);
  379. // Create rounded rectangle for button background/outline
  380. CGMutablePathRef path = CGPathCreateMutable();
  381. CGPathAddRoundedRect(path,
  382. NULL,
  383. CGRectInset(self.bounds, kBorderWidth, kBorderWidth),
  384. kCornerRadius,
  385. kCornerRadius);
  386. // Fill the background and apply halo shadow
  387. CGContextSaveGState(context);
  388. CGContextAddPath(context, path);
  389. CGContextSetFillColorWithColor(context, background.CGColor);
  390. // If we're not in the disabled state, we want a shadow
  391. if (_buttonState != kGIDSignInButtonStateDisabled) {
  392. // Draw halo shadow around button
  393. CGContextSetShadowWithColor(context,
  394. CGSizeMake(0, 0),
  395. kHaloShadowBlur,
  396. [UIColor colorWithWhite:0 alpha:kHaloShadowAlpha].CGColor);
  397. }
  398. CGContextFillPath(context);
  399. CGContextRestoreGState(context);
  400. if (_buttonState != kGIDSignInButtonStateDisabled) {
  401. // Fill the background again to apply drop shadow
  402. CGContextSaveGState(context);
  403. CGContextAddPath(context, path);
  404. CGContextSetFillColorWithColor(context, background.CGColor);
  405. CGContextSetShadowWithColor(context,
  406. CGSizeMake(0, kDropShadowYOffset),
  407. kDropShadowBlur,
  408. [UIColor colorWithWhite:0 alpha:kDropShadowAlpha].CGColor);
  409. CGContextFillPath(context);
  410. CGContextRestoreGState(context);
  411. }
  412. if (_colorScheme == kGIDSignInButtonColorSchemeDark &&
  413. _buttonState != kGIDSignInButtonStateDisabled) {
  414. // Create rounded rectangle container for the "G"
  415. CGMutablePathRef gContainerPath = CGPathCreateMutable();
  416. CGPathAddRoundedRect(gContainerPath,
  417. NULL,
  418. CGRectInset(CGRectMake(0, 0, kButtonHeight, kButtonHeight),
  419. kBorderWidth + 1,
  420. kBorderWidth + 1),
  421. kCornerRadius,
  422. kCornerRadius);
  423. CGContextAddPath(context, gContainerPath);
  424. CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor);
  425. CGContextFillPath(context);
  426. CGPathRelease(gContainerPath);
  427. }
  428. CGPathRelease(path);
  429. CGContextRestoreGState(context);
  430. }
  431. #pragma mark - Text Rendering
  432. - (void)drawButtonText:(CGContextRef)context {
  433. if (_style == kGIDSignInButtonStyleIconOnly) {
  434. return;
  435. }
  436. NSString *text = self.accessibilityLabel;
  437. UIColor *foregroundColor = colorForStyleState(_colorScheme,
  438. _buttonState,
  439. kGIDSignInButtonStyleColorForeground);
  440. UIFont *font = [[self class] buttonTextFont];
  441. CGSize textSize = [[self class] textSize:text withFont:font];
  442. // Draw the button text at the right position with the right color.
  443. CGFloat textLeft = kIconWidth + kTextPadding;
  444. CGFloat textTop = round((self.bounds.size.height - textSize.height) / 2);
  445. [text drawAtPoint:CGPointMake(textLeft, textTop)
  446. withAttributes:@{ NSFontAttributeName : font,
  447. NSForegroundColorAttributeName : foregroundColor }];
  448. }
  449. #pragma mark - Button Text Selection / Localization
  450. - (NSString *)buttonText {
  451. switch (_style) {
  452. case kGIDSignInButtonStyleWide:
  453. return [GIDSignInStrings signInWithGoogleString];
  454. case kGIDSignInButtonStyleStandard:
  455. case kGIDSignInButtonStyleIconOnly:
  456. return [GIDSignInStrings signInString];
  457. }
  458. }
  459. + (UIFont *)buttonTextFont {
  460. UIFont *font = [UIFont fontWithName:kFontNameRobotoBold size:kFontSize];
  461. if (!font) {
  462. font = [UIFont boldSystemFontOfSize:kFontSize];
  463. }
  464. return font;
  465. }
  466. + (CGSize)textSize:(NSString *)text withFont:(UIFont *)font {
  467. return [text boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX)
  468. options:0
  469. attributes:@{ NSFontAttributeName : font }
  470. context:nil].size;
  471. }
  472. @end
  473. #pragma mark - UIImage GIDAdditions_Private Category
  474. @implementation UIImage (GIDAdditions_Private)
  475. - (UIImage *)gid_imageWithBlendMode:(CGBlendMode)blendMode color:(UIColor *)color {
  476. CGSize size = [self size];
  477. CGRect rect = CGRectMake(0.0f, 0.0f, size.width, size.height);
  478. UIGraphicsBeginImageContextWithOptions(rect.size, NO, 0.0);
  479. CGContextRef context = UIGraphicsGetCurrentContext();
  480. CGContextSetShouldAntialias(context, true);
  481. CGContextSetInterpolationQuality(context, kCGInterpolationHigh);
  482. CGContextScaleCTM(context, 1, -1);
  483. CGContextTranslateCTM(context, 0, -rect.size.height);
  484. CGContextClipToMask(context, rect, self.CGImage);
  485. CGContextDrawImage(context, rect, self.CGImage);
  486. CGContextSetBlendMode(context, blendMode);
  487. CGFloat alpha = 1.0;
  488. if (blendMode == kCGBlendModeMultiply) {
  489. CGFloat red, green, blue;
  490. BOOL success = [color getRed:&red green:&green blue:&blue alpha:&alpha];
  491. if (success) {
  492. color = [UIColor colorWithRed:red green:green blue:blue alpha:1.0];
  493. } else {
  494. CGFloat grayscale;
  495. success = [color getWhite:&grayscale alpha:&alpha];
  496. if (success) {
  497. color = [UIColor colorWithWhite:grayscale alpha:1.0];
  498. }
  499. }
  500. }
  501. CGContextSetFillColorWithColor(context, color.CGColor);
  502. CGContextFillRect(context, rect);
  503. if (blendMode == kCGBlendModeMultiply && alpha != 1.0) {
  504. // Modulate by the alpha.
  505. color = [UIColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:alpha];
  506. CGContextSetBlendMode(context, kCGBlendModeDestinationIn);
  507. CGContextSetFillColorWithColor(context, color.CGColor);
  508. CGContextFillRect(context, rect);
  509. }
  510. UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
  511. UIGraphicsEndImageContext();
  512. if (self.capInsets.bottom > 0 || self.capInsets.top > 0 ||
  513. self.capInsets.left > 0 || self.capInsets.left > 0) {
  514. image = [image resizableImageWithCapInsets:self.capInsets];
  515. }
  516. return image;
  517. }
  518. @end
  519. NS_ASSUME_NONNULL_END