GoogleSignInButton.swift 6.4 KB

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