GoogleSignInButton.swift 5.2 KB

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