GoogleSignInButton.swift 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. /*
  2. * Copyright 2022 Google LLC
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #if !arch(arm) && !arch(i386)
  17. import SwiftUI
  18. import CoreGraphics
  19. // MARK: - Sign-In Button
  20. /// A Google Sign In button to be used in SwiftUI.
  21. @available(iOS 13.0, macOS 10.15, *)
  22. public struct GoogleSignInButton: View {
  23. /// An object containing the styling information needed to create the button.
  24. @ObservedObject public var viewModel: GoogleSignInButtonViewModel
  25. private let action: () -> Void
  26. private let fontLoaded: Bool
  27. /// Creates an instance of the Google Sign-In button for use in SwiftUI
  28. /// - parameter viewModel: An instance of `GoogleSignInButtonViewModel`
  29. /// containing information on the button's scheme, style, and state.
  30. /// Defaults to `GoogleSignInButtonViewModel` with its standard defaults.
  31. /// - parameter action: A closure to use as the button's action upon press.
  32. /// - seealso: Refer to `GoogleSignInButtonViewModel.swift` for its defaults.
  33. public init(
  34. viewModel: GoogleSignInButtonViewModel = GoogleSignInButtonViewModel(),
  35. action: @escaping () -> Void
  36. ) {
  37. self.viewModel = viewModel
  38. self.action = action
  39. self.fontLoaded = Font.loadCGFont()
  40. }
  41. /// A convenience initializer to create a Google Sign-In button in SwiftUI
  42. /// with scheme, style, and state.
  43. /// - parameter scheme: The `GoogleSignInButtonColorScheme` to use. Defaults
  44. /// to `.light`.
  45. /// - parameter style: The `GoogleSignInButtonStyle` to use. Defaults to
  46. /// `.standard`.
  47. /// - parameter state: The `GoogleSignInButtonState` to use. Defaults to
  48. /// `.normal`.
  49. /// - parameter action: A closure to use as the button's action upon press.
  50. public init(
  51. scheme: GoogleSignInButtonColorScheme = .light,
  52. style: GoogleSignInButtonStyle = .standard,
  53. state: GoogleSignInButtonState = .normal,
  54. action: @escaping () -> Void
  55. ) {
  56. let vm = GoogleSignInButtonViewModel(
  57. scheme: scheme,
  58. style: style,
  59. state: state
  60. )
  61. self.init(viewModel: vm, action: action)
  62. }
  63. public var body: some View {
  64. Button(action: action) {
  65. switch viewModel.style {
  66. case .icon:
  67. ZStack {
  68. RoundedRectangle(cornerRadius: googleCornerRadius)
  69. .fill(viewModel.buttonStyle.colors.iconColor)
  70. .border(viewModel.buttonStyle.colors.iconBorderColor)
  71. Image.signInButtonImage
  72. }
  73. case .standard, .wide:
  74. HStack(alignment: .center) {
  75. ZStack {
  76. RoundedRectangle(cornerRadius: googleCornerRadius)
  77. .fill(
  78. viewModel.state == .disabled ?
  79. .clear : viewModel.buttonStyle.colors.iconColor
  80. )
  81. .frame(
  82. width: iconWidth - iconPadding,
  83. height: iconWidth - iconPadding
  84. )
  85. Image.signInButtonImage
  86. }
  87. .padding(.leading, 1)
  88. Text(viewModel.style.buttonText)
  89. .fixedSize()
  90. .padding(.trailing, textPadding)
  91. .frame(
  92. width: viewModel.style.widthForButtonText,
  93. height: buttonHeight,
  94. alignment: .leading
  95. )
  96. Spacer()
  97. }
  98. }
  99. }
  100. .font(signInButtonFont)
  101. .buttonStyle(viewModel.buttonStyle)
  102. .disabled(viewModel.state == .disabled)
  103. .simultaneousGesture(
  104. DragGesture(minimumDistance: 0)
  105. .onChanged { _ in
  106. guard viewModel.state != .disabled,
  107. viewModel.state != .pressed else { return }
  108. viewModel.state = .pressed
  109. }
  110. .onEnded { _ in
  111. guard viewModel.state != .disabled else { return }
  112. viewModel.state = .normal
  113. }
  114. )
  115. }
  116. }
  117. // MARK: - Google Icon Image
  118. @available(iOS 13.0, macOS 10.15, *)
  119. private extension Image {
  120. static var signInButtonImage: Image {
  121. guard let iconURL = Bundle.urlForGoogleResource(
  122. name: googleImageName,
  123. withExtension: "png"
  124. ) else {
  125. fatalError("Unable to load Google icon image url: \(Image.Error.unableToLoadGoogleIcon(name: googleImageName))")
  126. }
  127. #if os(iOS) || targetEnvironment(macCatalyst)
  128. guard let uiImage = UIImage(contentsOfFile: iconURL.path) else {
  129. fatalError("Unable to load Google icon image url: \(Image.Error.unableToLoadGoogleIcon(name: googleImageName))")
  130. }
  131. return Image(uiImage: uiImage)
  132. #elseif os(macOS)
  133. guard let nsImage = NSImage(contentsOfFile: iconURL.path) else {
  134. fatalError("Unable to load Google icon image url: \(Image.Error.unableToLoadGoogleIcon(name: googleImageName))")
  135. }
  136. return Image(nsImage: nsImage)
  137. #else
  138. fatalError("Unrecognized platform for SwiftUI sign in button image")
  139. #endif
  140. }
  141. enum Error: Swift.Error {
  142. case unableToLoadGoogleIcon(name: String)
  143. }
  144. }
  145. // MARK: - Google Font
  146. @available(iOS 13.0, macOS 10.15, *)
  147. private extension GoogleSignInButton {
  148. var signInButtonFont: Font {
  149. guard fontLoaded else {
  150. return .system(size: fontSize, weight: .bold)
  151. }
  152. return .custom(fontNameRobotoBold, size: fontSize)
  153. }
  154. }
  155. @available(iOS 13.0, macOS 10.15, *)
  156. private extension Font {
  157. /// Load the font for the button.
  158. /// - returns A `Bool` indicating whether or not the font was loaded.
  159. static func loadCGFont() -> Bool {
  160. // Check to see if the font has already been loaded
  161. #if os(iOS) || targetEnvironment(macCatalyst)
  162. if let _ = UIFont(name: fontNameRobotoBold, size: fontSize) {
  163. return true
  164. }
  165. #elseif os(macOS)
  166. if let _ = NSFont(name: fontNameRobotoBold, size: fontSize) {
  167. return true
  168. }
  169. #else
  170. fatalError("Unrecognized platform for SwiftUI sign in button font")
  171. #endif
  172. guard let fontURL = Bundle.urlForGoogleResource(
  173. name: fontNameRobotoBold,
  174. withExtension: "ttf"
  175. ), let dataProvider = CGDataProvider(filename: fontURL.path),
  176. let newFont = CGFont(dataProvider) else {
  177. return false
  178. }
  179. return CTFontManagerRegisterGraphicsFont(newFont, nil)
  180. }
  181. }
  182. #endif // !arch(arm) && !arch(i386)