GIDSignInButton.m 21 KB

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