GoogleSignInButton.swift 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  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. .padding(.trailing, textPadding)
  64. Spacer()
  65. }
  66. }
  67. }
  68. .font(signInButtonFont)
  69. .buttonStyle(viewModel.buttonStyle)
  70. .disabled(viewModel.state == .disabled)
  71. .simultaneousGesture(
  72. DragGesture(minimumDistance: 0)
  73. .onChanged { _ in
  74. guard viewModel.state != .disabled,
  75. viewModel.state != .pressed else { return }
  76. viewModel.state = .pressed
  77. }
  78. .onEnded { _ in
  79. guard viewModel.state != .disabled else { return }
  80. viewModel.state = .normal
  81. }
  82. )
  83. }
  84. }
  85. // MARK: - Google Icon Image
  86. @available(iOS 13.0, macOS 10.15, *)
  87. private extension Image {
  88. static var signInButtonImage: Image {
  89. guard let iconURL = Bundle.urlForGoogleResource(
  90. name: googleImageName,
  91. withExtension: "png"
  92. ) else {
  93. fatalError("Unable to load Google icon image url: \(Image.Error.unableToLoadGoogleIcon(name: googleImageName))")
  94. }
  95. #if os(iOS) || targetEnvironment(macCatalyst)
  96. guard let uiImage = UIImage(contentsOfFile: iconURL.path) else {
  97. fatalError("Unable to load Google icon image url: \(Image.Error.unableToLoadGoogleIcon(name: googleImageName))")
  98. }
  99. return Image(uiImage: uiImage)
  100. #elseif os(macOS)
  101. guard let nsImage = NSImage(contentsOfFile: iconURL.path) else {
  102. fatalError("Unable to load Google icon image url: \(Image.Error.unableToLoadGoogleIcon(name: googleImageName))")
  103. }
  104. return Image(nsImage: nsImage)
  105. #else
  106. fatalError("Unrecognized platform for SwiftUI sign in button image")
  107. #endif
  108. }
  109. enum Error: Swift.Error {
  110. case unableToLoadGoogleIcon(name: String)
  111. }
  112. }
  113. // MARK: - Google Font
  114. @available(iOS 13.0, macOS 10.15, *)
  115. private extension GoogleSignInButton {
  116. var signInButtonFont: Font {
  117. guard fontLoaded else {
  118. return .system(size: fontSize, weight: .bold)
  119. }
  120. return .custom(fontNameRobotoBold, size: fontSize)
  121. }
  122. }
  123. @available(iOS 13.0, macOS 10.15, *)
  124. private extension Font {
  125. /// Load the font for the button.
  126. /// - returns A `Bool` indicating whether or not the font was loaded.
  127. static func loadCGFont() -> Bool {
  128. // Check to see if the font has already been loaded
  129. #if os(iOS) || targetEnvironment(macCatalyst)
  130. if let _ = UIFont(name: fontNameRobotoBold, size: fontSize) {
  131. return true
  132. }
  133. #elseif os(macOS)
  134. if let _ = NSFont(name: fontNameRobotoBold, size: fontSize) {
  135. return true
  136. }
  137. #else
  138. fatalError("Unrecognized platform for SwiftUI sign in button font")
  139. #endif
  140. guard let fontURL = Bundle.urlForGoogleResource(
  141. name: fontNameRobotoBold,
  142. withExtension: "ttf"
  143. ), let dataProvider = CGDataProvider(filename: fontURL.path),
  144. let newFont = CGFont(dataProvider) else {
  145. return false
  146. }
  147. return CTFontManagerRegisterGraphicsFont(newFont, nil)
  148. }
  149. }