GoogleSignInButton.swift 6.2 KB

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