GIDSignInButton.m 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652
  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. [aCoder encodeInteger:_style forKey:kStyleKey];
  177. [aCoder encodeInteger:_colorScheme forKey:kColorSchemeKey];
  178. [aCoder encodeInteger:_buttonState forKey:kButtonState];
  179. }
  180. #pragma mark - NSSecureCoding
  181. + (BOOL)supportsSecureCoding {
  182. return YES;
  183. }
  184. #pragma mark - UI
  185. - (void)updateUI {
  186. // Reload the icon.
  187. [self loadIcon];
  188. // Set a useful accessibility label here even if we're not showing text.
  189. // Get localized button text from bundle.
  190. self.accessibilityLabel = [self buttonText];
  191. // Force constrain frame sizes:
  192. [self setFrame:self.frame];
  193. [self setNeedsUpdateConstraints];
  194. [self setNeedsDisplay];
  195. }
  196. - (void)loadIcon {
  197. NSString *resourceName = kGoogleImageName;
  198. NSBundle *gidBundle = [NSBundle gid_frameworkBundle];
  199. NSString *resourcePath = [gidBundle pathForResource:resourceName ofType:@"png"];
  200. UIImage *image = [UIImage imageWithContentsOfFile:resourcePath];
  201. if (_buttonState == kGIDSignInButtonStateDisabled) {
  202. _icon.image = [image gid_imageWithBlendMode:kCGBlendModeMultiply
  203. color:[UIColor colorWithWhite:0
  204. alpha:kDisabledIconAlpha]];
  205. } else {
  206. _icon.image = image;
  207. }
  208. }
  209. #pragma mark - State Transitions
  210. - (void)switchToPressed {
  211. [self setButtonState:kGIDSignInButtonStatePressed];
  212. }
  213. - (void)switchToNormal {
  214. [self setButtonState:kGIDSignInButtonStateNormal];
  215. }
  216. - (void)switchToDisabled {
  217. [self setButtonState:kGIDSignInButtonStateDisabled];
  218. }
  219. #pragma mark - Properties
  220. - (void)setStyle:(GIDSignInButtonStyle)style {
  221. if (style == _style) {
  222. return;
  223. }
  224. _style = style;
  225. [self updateUI];
  226. }
  227. - (void)setColorScheme:(GIDSignInButtonColorScheme)colorScheme {
  228. if (colorScheme == _colorScheme) {
  229. return;
  230. }
  231. _colorScheme = colorScheme;
  232. [self updateUI];
  233. }
  234. - (void)setEnabled:(BOOL)enabled {
  235. if (enabled == self.enabled) {
  236. return;
  237. }
  238. [super setEnabled:enabled];
  239. if (enabled) {
  240. [self switchToNormal];
  241. } else {
  242. [self switchToDisabled];
  243. }
  244. [self updateUI];
  245. }
  246. - (void)setButtonState:(GIDSignInButtonState)buttonState {
  247. if (buttonState == _buttonState) {
  248. return;
  249. }
  250. _buttonState = buttonState;
  251. [self setNeedsDisplay];
  252. }
  253. - (void)setFrame:(CGRect)frame {
  254. // Constrain the frame size to sizes we want.
  255. frame.size = [self sizeThatFits:frame.size];
  256. if (CGRectEqualToRect(frame, self.frame)) {
  257. return;
  258. }
  259. [super setFrame:frame];
  260. [self setNeedsUpdateConstraints];
  261. [self setNeedsDisplay];
  262. }
  263. #pragma mark - Helpers
  264. - (CGFloat)minWidth {
  265. if (_style == kGIDSignInButtonStyleIconOnly) {
  266. return kIconWidth + (kBorderWidth * 2);
  267. }
  268. NSString *text = [self buttonText];
  269. CGSize textSize = [[self class] textSize:text withFont:[[self class] buttonTextFont]];
  270. return ceil(kIconWidth + (kTextPadding * 2) + textSize.width + (kBorderWidth * 2));
  271. }
  272. - (BOOL)isConstraint:(NSLayoutConstraint *)constraintA
  273. equalToConstraint:(NSLayoutConstraint *)constraintB {
  274. return constraintA.priority == constraintB.priority &&
  275. constraintA.firstItem == constraintB.firstItem &&
  276. constraintA.firstAttribute == constraintB.firstAttribute &&
  277. constraintA.relation == constraintB.relation &&
  278. constraintA.secondItem == constraintB.secondItem &&
  279. constraintA.secondAttribute == constraintB.secondAttribute &&
  280. constraintA.multiplier == constraintB.multiplier &&
  281. constraintA.constant == constraintB.constant;
  282. }
  283. #pragma mark - Overrides
  284. - (CGSize)sizeThatFits:(CGSize)size {
  285. switch (_style) {
  286. case kGIDSignInButtonStyleIconOnly:
  287. return CGSizeMake([self minWidth], kButtonHeight);
  288. case kGIDSignInButtonStyleStandard:
  289. case kGIDSignInButtonStyleWide: {
  290. return CGSizeMake(MAX(size.width, [self minWidth]), kButtonHeight);
  291. }
  292. }
  293. }
  294. - (void)updateConstraints {
  295. NSLayoutRelation widthConstraintRelation;
  296. // For icon style, we want to ensure a fixed width
  297. if (_style == kGIDSignInButtonStyleIconOnly) {
  298. widthConstraintRelation = NSLayoutRelationEqual;
  299. } else {
  300. widthConstraintRelation = NSLayoutRelationGreaterThanOrEqual;
  301. }
  302. // Define a width constraint ensuring that we don't go below our minimum width
  303. NSLayoutConstraint *widthConstraint =
  304. [NSLayoutConstraint constraintWithItem:self
  305. attribute:NSLayoutAttributeWidth
  306. relatedBy:widthConstraintRelation
  307. toItem:nil
  308. attribute:NSLayoutAttributeNotAnAttribute
  309. multiplier:1.0
  310. constant:[self minWidth]];
  311. widthConstraint.identifier = @"buttonWidth - auto generated by GIDSignInButton";
  312. // Define a height constraint using our constant height
  313. NSLayoutConstraint *heightConstraint =
  314. [NSLayoutConstraint constraintWithItem:self
  315. attribute:NSLayoutAttributeHeight
  316. relatedBy:NSLayoutRelationEqual
  317. toItem:nil
  318. attribute:NSLayoutAttributeNotAnAttribute
  319. multiplier:1.0
  320. constant:kButtonHeight];
  321. heightConstraint.identifier = @"buttonHeight - auto generated by GIDSignInButton";
  322. // By default, add our width and height constraints
  323. BOOL addWidthConstraint = YES;
  324. BOOL addHeightConstraint = YES;
  325. for (NSLayoutConstraint *constraint in self.constraints) {
  326. // If it is equivalent to our width or height constraint, don't add ours later
  327. if ([self isConstraint:constraint equalToConstraint:widthConstraint]) {
  328. addWidthConstraint = NO;
  329. continue;
  330. }
  331. if ([self isConstraint:constraint equalToConstraint:heightConstraint]) {
  332. addHeightConstraint = NO;
  333. continue;
  334. }
  335. if (constraint.firstItem == self) {
  336. // If it is a height constraint of any relation, remove it
  337. if (constraint.firstAttribute == NSLayoutAttributeHeight) {
  338. [self removeConstraint:constraint];
  339. }
  340. // If it is a width constraint of any relation, remove it if it will conflict with ours
  341. if (constraint.firstAttribute == NSLayoutAttributeWidth &&
  342. (constraint.constant < [self minWidth] || _style == kGIDSignInButtonStyleIconOnly)) {
  343. [self removeConstraint:constraint];
  344. }
  345. }
  346. }
  347. if (addWidthConstraint) {
  348. [self addConstraint:widthConstraint];
  349. }
  350. if (addHeightConstraint) {
  351. [self addConstraint:heightConstraint];
  352. }
  353. [super updateConstraints];
  354. }
  355. #pragma mark - Rendering
  356. - (void)drawRect:(CGRect)rect {
  357. [super drawRect:rect];
  358. CGContextRef context = UIGraphicsGetCurrentContext();
  359. CGContextRetain(context);
  360. if (context == NULL) {
  361. return;
  362. }
  363. // Draw the button background
  364. [self drawButtonBackground:context];
  365. // Draw the text
  366. [self drawButtonText:context];
  367. CGContextRelease(context);
  368. context = NULL;
  369. }
  370. #pragma mark - Button Background Rendering
  371. - (void)drawButtonBackground:(CGContextRef)context {
  372. CGContextSaveGState(context);
  373. // Normalize the coordinate system of our graphics context
  374. // (0,0) -----> +x
  375. // |
  376. // |
  377. // \/ +y
  378. CGContextScaleCTM(context, 1, -1);
  379. CGContextTranslateCTM(context, 0, -self.bounds.size.height);
  380. // Get the colors for the current state and configuration
  381. UIColor *background = colorForStyleState(_colorScheme,
  382. _buttonState,
  383. kGIDSignInButtonStyleColorBackground);
  384. // Create rounded rectangle for button background/outline
  385. CGMutablePathRef path = CGPathCreateMutable();
  386. CGPathAddRoundedRect(path,
  387. NULL,
  388. CGRectInset(self.bounds, kBorderWidth, kBorderWidth),
  389. kCornerRadius,
  390. kCornerRadius);
  391. // Fill the background and apply halo shadow
  392. CGContextSaveGState(context);
  393. CGContextAddPath(context, path);
  394. CGContextSetFillColorWithColor(context, background.CGColor);
  395. // If we're not in the disabled state, we want a shadow
  396. if (_buttonState != kGIDSignInButtonStateDisabled) {
  397. // Draw halo shadow around button
  398. CGContextSetShadowWithColor(context,
  399. CGSizeMake(0, 0),
  400. kHaloShadowBlur,
  401. [UIColor colorWithWhite:0 alpha:kHaloShadowAlpha].CGColor);
  402. }
  403. CGContextFillPath(context);
  404. CGContextRestoreGState(context);
  405. if (_buttonState != kGIDSignInButtonStateDisabled) {
  406. // Fill the background again to apply drop shadow
  407. CGContextSaveGState(context);
  408. CGContextAddPath(context, path);
  409. CGContextSetFillColorWithColor(context, background.CGColor);
  410. CGContextSetShadowWithColor(context,
  411. CGSizeMake(0, kDropShadowYOffset),
  412. kDropShadowBlur,
  413. [UIColor colorWithWhite:0 alpha:kDropShadowAlpha].CGColor);
  414. CGContextFillPath(context);
  415. CGContextRestoreGState(context);
  416. }
  417. if (_colorScheme == kGIDSignInButtonColorSchemeDark &&
  418. _buttonState != kGIDSignInButtonStateDisabled) {
  419. // Create rounded rectangle container for the "G"
  420. CGMutablePathRef gContainerPath = CGPathCreateMutable();
  421. CGPathAddRoundedRect(gContainerPath,
  422. NULL,
  423. CGRectInset(CGRectMake(0, 0, kButtonHeight, kButtonHeight),
  424. kBorderWidth + 1,
  425. kBorderWidth + 1),
  426. kCornerRadius,
  427. kCornerRadius);
  428. CGContextAddPath(context, gContainerPath);
  429. CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor);
  430. CGContextFillPath(context);
  431. CGPathRelease(gContainerPath);
  432. }
  433. CGPathRelease(path);
  434. CGContextRestoreGState(context);
  435. }
  436. #pragma mark - Text Rendering
  437. - (void)drawButtonText:(CGContextRef)context {
  438. if (_style == kGIDSignInButtonStyleIconOnly) {
  439. return;
  440. }
  441. NSString *text = self.accessibilityLabel;
  442. UIColor *foregroundColor = colorForStyleState(_colorScheme,
  443. _buttonState,
  444. kGIDSignInButtonStyleColorForeground);
  445. UIFont *font = [[self class] buttonTextFont];
  446. CGSize textSize = [[self class] textSize:text withFont:font];
  447. // Draw the button text at the right position with the right color.
  448. CGFloat textLeft = kIconWidth + kTextPadding;
  449. CGFloat textTop = round((self.bounds.size.height - textSize.height) / 2);
  450. [text drawAtPoint:CGPointMake(textLeft, textTop)
  451. withAttributes:@{ NSFontAttributeName : font,
  452. NSForegroundColorAttributeName : foregroundColor }];
  453. }
  454. #pragma mark - Button Text Selection / Localization
  455. - (NSString *)buttonText {
  456. switch (_style) {
  457. case kGIDSignInButtonStyleWide:
  458. return [GIDSignInStrings signInWithGoogleString];
  459. case kGIDSignInButtonStyleStandard:
  460. case kGIDSignInButtonStyleIconOnly:
  461. return [GIDSignInStrings signInString];
  462. }
  463. }
  464. + (UIFont *)buttonTextFont {
  465. UIFont *font = [UIFont fontWithName:kFontNameRobotoBold size:kFontSize];
  466. if (!font) {
  467. font = [UIFont boldSystemFontOfSize:kFontSize];
  468. }
  469. return font;
  470. }
  471. + (CGSize)textSize:(NSString *)text withFont:(UIFont *)font {
  472. return [text boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX)
  473. options:0
  474. attributes:@{ NSFontAttributeName : font }
  475. context:nil].size;
  476. }
  477. @end
  478. #pragma mark - UIImage GIDAdditions_Private Category
  479. @implementation UIImage (GIDAdditions_Private)
  480. - (UIImage *)gid_imageWithBlendMode:(CGBlendMode)blendMode color:(UIColor *)color {
  481. CGSize size = [self size];
  482. CGRect rect = CGRectMake(0.0f, 0.0f, size.width, size.height);
  483. UIGraphicsBeginImageContextWithOptions(rect.size, NO, 0.0);
  484. CGContextRef context = UIGraphicsGetCurrentContext();
  485. CGContextSetShouldAntialias(context, true);
  486. CGContextSetInterpolationQuality(context, kCGInterpolationHigh);
  487. CGContextScaleCTM(context, 1, -1);
  488. CGContextTranslateCTM(context, 0, -rect.size.height);
  489. CGContextClipToMask(context, rect, self.CGImage);
  490. CGContextDrawImage(context, rect, self.CGImage);
  491. CGContextSetBlendMode(context, blendMode);
  492. CGFloat alpha = 1.0;
  493. if (blendMode == kCGBlendModeMultiply) {
  494. CGFloat red, green, blue;
  495. BOOL success = [color getRed:&red green:&green blue:&blue alpha:&alpha];
  496. if (success) {
  497. color = [UIColor colorWithRed:red green:green blue:blue alpha:1.0];
  498. } else {
  499. CGFloat grayscale;
  500. success = [color getWhite:&grayscale alpha:&alpha];
  501. if (success) {
  502. color = [UIColor colorWithWhite:grayscale alpha:1.0];
  503. }
  504. }
  505. }
  506. CGContextSetFillColorWithColor(context, color.CGColor);
  507. CGContextFillRect(context, rect);
  508. if (blendMode == kCGBlendModeMultiply && alpha != 1.0) {
  509. // Modulate by the alpha.
  510. color = [UIColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:alpha];
  511. CGContextSetBlendMode(context, kCGBlendModeDestinationIn);
  512. CGContextSetFillColorWithColor(context, color.CGColor);
  513. CGContextFillRect(context, rect);
  514. }
  515. UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
  516. UIGraphicsEndImageContext();
  517. if (self.capInsets.bottom > 0 || self.capInsets.top > 0 ||
  518. self.capInsets.left > 0 || self.capInsets.left > 0) {
  519. image = [image resizableImageWithCapInsets:self.capInsets];
  520. }
  521. return image;
  522. }
  523. @end
  524. NS_ASSUME_NONNULL_END
  525. #endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST