GoogleSignInButton.swift 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  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. public var body: some View {
  40. Button(action: action) {
  41. switch viewModel.style {
  42. case .icon:
  43. ZStack {
  44. RoundedRectangle(cornerRadius: googleCornerRadius)
  45. .fill(viewModel.buttonStyle.colors.iconColor)
  46. .border(viewModel.buttonStyle.colors.iconBorderColor)
  47. Image.signInButtonImage
  48. }
  49. case .standard, .wide:
  50. HStack(alignment: .center) {
  51. ZStack {
  52. RoundedRectangle(cornerRadius: googleCornerRadius)
  53. .fill(
  54. viewModel.state == .disabled ?
  55. .clear : viewModel.buttonStyle.colors.iconColor
  56. )
  57. .frame(
  58. width: iconWidth - iconPadding,
  59. height: iconWidth - iconPadding
  60. )
  61. Image.signInButtonImage
  62. }
  63. .padding(.leading, 1)
  64. Text(viewModel.style.buttonText)
  65. .fixedSize()
  66. .padding(.trailing, textPadding)
  67. .frame(
  68. width: viewModel.style.widthForButtonText,
  69. height: buttonHeight,
  70. alignment: .leading
  71. )
  72. Spacer()
  73. }
  74. }
  75. }
  76. .font(signInButtonFont)
  77. .buttonStyle(viewModel.buttonStyle)
  78. .disabled(viewModel.state == .disabled)
  79. .simultaneousGesture(
  80. DragGesture(minimumDistance: 0)
  81. .onChanged { _ in
  82. guard viewModel.state != .disabled,
  83. viewModel.state != .pressed else { return }
  84. viewModel.state = .pressed
  85. }
  86. .onEnded { _ in
  87. guard viewModel.state != .disabled else { return }
  88. viewModel.state = .normal
  89. }
  90. )
  91. }
  92. }
  93. // MARK: - Google Icon Image
  94. @available(iOS 13.0, macOS 10.15, *)
  95. private extension Image {
  96. static var signInButtonImage: Image {
  97. guard let iconURL = Bundle.urlForGoogleResource(
  98. name: googleImageName,
  99. withExtension: "png"
  100. ) else {
  101. fatalError("Unable to load Google icon image url: \(Image.Error.unableToLoadGoogleIcon(name: googleImageName))")
  102. }
  103. #if os(iOS) || targetEnvironment(macCatalyst)
  104. guard let uiImage = UIImage(contentsOfFile: iconURL.path) else {
  105. fatalError("Unable to load Google icon image url: \(Image.Error.unableToLoadGoogleIcon(name: googleImageName))")
  106. }
  107. return Image(uiImage: uiImage)
  108. #elseif os(macOS)
  109. guard let nsImage = NSImage(contentsOfFile: iconURL.path) else {
  110. fatalError("Unable to load Google icon image url: \(Image.Error.unableToLoadGoogleIcon(name: googleImageName))")
  111. }
  112. return Image(nsImage: nsImage)
  113. #else
  114. fatalError("Unrecognized platform for SwiftUI sign in button image")
  115. #endif
  116. }
  117. enum Error: Swift.Error {
  118. case unableToLoadGoogleIcon(name: String)
  119. }
  120. }
  121. // MARK: - Google Font
  122. @available(iOS 13.0, macOS 10.15, *)
  123. private extension GoogleSignInButton {
  124. var signInButtonFont: Font {
  125. guard fontLoaded else {
  126. return .system(size: fontSize, weight: .bold)
  127. }
  128. return .custom(fontNameRobotoBold, size: fontSize)
  129. }
  130. }
  131. @available(iOS 13.0, macOS 10.15, *)
  132. private extension Font {
  133. /// Load the font for the button.
  134. /// - returns A `Bool` indicating whether or not the font was loaded.
  135. static func loadCGFont() -> Bool {
  136. // Check to see if the font has already been loaded
  137. #if os(iOS) || targetEnvironment(macCatalyst)
  138. if let _ = UIFont(name: fontNameRobotoBold, size: fontSize) {
  139. return true
  140. }
  141. #elseif os(macOS)
  142. if let _ = NSFont(name: fontNameRobotoBold, size: fontSize) {
  143. return true
  144. }
  145. #else
  146. fatalError("Unrecognized platform for SwiftUI sign in button font")
  147. #endif
  148. guard let fontURL = Bundle.urlForGoogleResource(
  149. name: fontNameRobotoBold,
  150. withExtension: "ttf"
  151. ), let dataProvider = CGDataProvider(filename: fontURL.path),
  152. let newFont = CGFont(dataProvider) else {
  153. return false
  154. }
  155. return CTFontManagerRegisterGraphicsFont(newFont, nil)
  156. }
  157. }