From 8c885e7546a34b981d45fff1b0cbd5c41b1b9ff6 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Tue, 27 Oct 2026 17:55:24 -0400 Subject: [PATCH 1/6] build(deps): update firebase-ios-sdk to 13.1.1 and resolve build configurations Update firebase-ios-sdk package dependency across all sample projects to minimumVersion 13.1.1 with upToNextMajorVersion. Also update GoogleSignIn-iOS to 10.1.0 and FirebaseUI-iOS to 16.2.0 where used. Address required build configuration updates for Xcode and Firebase 13: - Fix duplicate GoogleService-Info.plist tasks by moving to Resources phase - Add mock GoogleService-Info.plist to PasswordlessSwiftUI - Correct Info.plist path for AppDistributionExample test targets - Remove obsolete FirebaseDatabaseSwift and FirebaseAuthUI references - Add @MainActor isolation to AuthenticationExampleUITests - Conditionally apply deployment target overrides for Xcode 27+ in scripts/test.sh --- .../project.pbxproj | 2 +- .../project.pbxproj | 2 +- .../project.pbxproj | 19 +- .../project.pbxproj | 8 +- .../AuthenticationExampleUITests.swift | 408 ++++++------ .../ConfigExample.xcodeproj/project.pbxproj | 10 +- .../project.pbxproj | 2 +- .../DatabaseExample.xcodeproj/project.pbxproj | 11 +- .../DatabaseExample.xcodeproj/project.pbxproj | 26 +- .../project.pbxproj | 4 +- .../project.pbxproj | 37 +- .../RestaurantsTableViewController.swift | 598 +++++++++--------- .../project.pbxproj | 2 +- .../project.pbxproj | 7 +- .../project.pbxproj | 9 +- .../project.pbxproj | 9 +- .../project.pbxproj | 2 +- scripts/test.sh | 8 + .../StorageExample.xcodeproj/project.pbxproj | 2 +- .../project.pbxproj | 2 +- .../GoogleService-Info.plist | 40 ++ 21 files changed, 597 insertions(+), 611 deletions(-) create mode 100644 swiftui/PasswordlessSwiftUI/PasswordlessSwiftUI/GoogleService-Info.plist diff --git a/abtesting/ABTestingExample.xcodeproj/project.pbxproj b/abtesting/ABTestingExample.xcodeproj/project.pbxproj index 78c66e53f..a093dc367 100644 --- a/abtesting/ABTestingExample.xcodeproj/project.pbxproj +++ b/abtesting/ABTestingExample.xcodeproj/project.pbxproj @@ -1530,7 +1530,7 @@ repositoryURL = "https://github.com/firebase/firebase-ios-sdk.git"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 10.0.0; + minimumVersion = 13.1.1; }; }; /* End XCRemoteSwiftPackageReference section */ diff --git a/analytics/AnalyticsExample.xcodeproj/project.pbxproj b/analytics/AnalyticsExample.xcodeproj/project.pbxproj index 96688d2e9..61e3dd66b 100644 --- a/analytics/AnalyticsExample.xcodeproj/project.pbxproj +++ b/analytics/AnalyticsExample.xcodeproj/project.pbxproj @@ -1068,7 +1068,7 @@ repositoryURL = "https://github.com/firebase/firebase-ios-sdk"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 11.7.0; + minimumVersion = 13.1.1; }; }; /* End XCRemoteSwiftPackageReference section */ diff --git a/appdistribution/AppDistributionExample.xcodeproj/project.pbxproj b/appdistribution/AppDistributionExample.xcodeproj/project.pbxproj index cbf24951d..ad93d19d1 100644 --- a/appdistribution/AppDistributionExample.xcodeproj/project.pbxproj +++ b/appdistribution/AppDistributionExample.xcodeproj/project.pbxproj @@ -7,9 +7,7 @@ objects = { /* Begin PBXBuildFile section */ - 1E5FA399E68EEC6A1CC9D4F9 /* GoogleService-Info.plist in Sources */ = {isa = PBXBuildFile; fileRef = 7B7A16FC609E3AE1BA2F8E93 /* GoogleService-Info.plist */; }; - 4C5B56DE60EC55CE980B3E35 /* GoogleService-Info.plist in Sources */ = {isa = PBXBuildFile; fileRef = 7B7A16FC609E3AE1BA2F8E93 /* GoogleService-Info.plist */; }; - 64A8AA8B04202E42604E1B50 /* GoogleService-Info.plist in Sources */ = {isa = PBXBuildFile; fileRef = 7B7A16FC609E3AE1BA2F8E93 /* GoogleService-Info.plist */; }; + 1E5FA399E68EEC6A1CC9D4F9 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 7B7A16FC609E3AE1BA2F8E93 /* GoogleService-Info.plist */; }; 8D7951E32D3B02BA000FD694 /* FirebaseAnalytics in Frameworks */ = {isa = PBXBuildFile; productRef = 8D7951E22D3B02BA000FD694 /* FirebaseAnalytics */; }; 8D7951E52D3B02BA000FD694 /* FirebaseAppDistribution-Beta in Frameworks */ = {isa = PBXBuildFile; productRef = 8D7951E42D3B02BA000FD694 /* FirebaseAppDistribution-Beta */; }; EA5C0BC624C5D2060010FDF6 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA5C0BC524C5D2060010FDF6 /* AppDelegate.swift */; }; @@ -250,6 +248,8 @@ files = ( EA5C0BD224C5D2070010FDF6 /* LaunchScreen.storyboard in Resources */, EA5C0BCF24C5D2070010FDF6 /* Assets.xcassets in Resources */, + + 1E5FA399E68EEC6A1CC9D4F9 /* GoogleService-Info.plist in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -277,7 +277,6 @@ EA5C0BCA24C5D2060010FDF6 /* AppDistributionViewController.swift in Sources */, EA5C0BC624C5D2060010FDF6 /* AppDelegate.swift in Sources */, EA5C0BC824C5D2060010FDF6 /* SceneDelegate.swift in Sources */, - 1E5FA399E68EEC6A1CC9D4F9 /* GoogleService-Info.plist in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -286,7 +285,6 @@ buildActionMask = 2147483647; files = ( EA5C0BDD24C5D2070010FDF6 /* AppDistributionExampleTests.swift in Sources */, - 64A8AA8B04202E42604E1B50 /* GoogleService-Info.plist in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -295,7 +293,6 @@ buildActionMask = 2147483647; files = ( EA5C0BE824C5D2070010FDF6 /* AppDistributionExampleUITests.swift in Sources */, - 4C5B56DE60EC55CE980B3E35 /* GoogleService-Info.plist in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -492,7 +489,7 @@ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; - INFOPLIST_FILE = AppDistributionExampleTests/Info.plist; + INFOPLIST_FILE = AppDistributionTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -513,7 +510,7 @@ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; - INFOPLIST_FILE = AppDistributionExampleTests/Info.plist; + INFOPLIST_FILE = AppDistributionTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -533,7 +530,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - INFOPLIST_FILE = AppDistributionExampleUITests/Info.plist; + INFOPLIST_FILE = AppDistributionUITests/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -552,7 +549,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - INFOPLIST_FILE = AppDistributionExampleUITests/Info.plist; + INFOPLIST_FILE = AppDistributionUITests/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -613,7 +610,7 @@ repositoryURL = "https://github.com/firebase/firebase-ios-sdk"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 11.7.0; + minimumVersion = 13.1.1; }; }; /* End XCRemoteSwiftPackageReference section */ diff --git a/authentication/AuthenticationExample.xcodeproj/project.pbxproj b/authentication/AuthenticationExample.xcodeproj/project.pbxproj index b664c058d..f5956f29e 100644 --- a/authentication/AuthenticationExample.xcodeproj/project.pbxproj +++ b/authentication/AuthenticationExample.xcodeproj/project.pbxproj @@ -760,15 +760,15 @@ repositoryURL = "https://github.com/firebase/firebase-ios-sdk"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 11.7.0; + minimumVersion = 13.1.1; }; }; 8D7951E72D3B037B000FD694 /* XCRemoteSwiftPackageReference "GoogleSignIn-iOS" */ = { isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/paulb777/GoogleSignIn-iOS.git"; + repositoryURL = "https://github.com/google/GoogleSignIn-iOS.git"; requirement = { - branch = "pb-test-swift-app-check"; - kind = branch; + kind = upToNextMajorVersion; + minimumVersion = 10.1.0; }; }; /* End XCRemoteSwiftPackageReference section */ diff --git a/authentication/AuthenticationExampleUITests/AuthenticationExampleUITests.swift b/authentication/AuthenticationExampleUITests/AuthenticationExampleUITests.swift index 0fceb3bc9..7c3d3a8b1 100644 --- a/authentication/AuthenticationExampleUITests/AuthenticationExampleUITests.swift +++ b/authentication/AuthenticationExampleUITests/AuthenticationExampleUITests.swift @@ -14,244 +14,246 @@ import XCTest +@MainActor class AuthenticationExampleUITests: XCTestCase { - var app: XCUIApplication! + var app: XCUIApplication! - override func setUp() { - super.setUp() + override func setUp() { + super.setUp() - continueAfterFailure = false + continueAfterFailure = false - app = XCUIApplication() - app.launch() - } + app = XCUIApplication() + app.launch() + } + + override func tearDown() { + super.tearDown() + signOut() + } + + func testAuth() { + // Verify that Auth Example app launched successfully + XCTAssertTrue(app.navigationBars["Firebase Auth"].exists) + } + + func testAuthOptions() { + // There are 12 sign in methods, each with its own cell + XCTAssertEqual(app.tables.cells.count, 12) + } + + func testAuthAnonymously() { + app.staticTexts["Anonymous Authentication"].tap() + + wait(forElement: app.navigationBars["User"], timeout: 5.0) + XCTAssertTrue(app.navigationBars["User"].exists) + + let isAnonymousCell = app.cells.containing(.staticText, identifier: "Is User Anonymous?") + .element + XCTAssertTrue(isAnonymousCell.staticTexts["Yes"].exists, "The user should be anonymous") + } + + func testAuthExistingAccount() { + app.staticTexts["Email & Password Login"].tap() + + let testEmail = "test@test.com" + app.textFields["Email"].tap() + app.textFields["Email"].typeText(testEmail) + + let testPassword = "test12" + app.textFields["Password"].tap() + app.textFields["Password"].typeText(testPassword) + + app.buttons["Login"].tap() + + wait(forElement: app.navigationBars["User"], timeout: 5.0) + XCTAssertTrue(app.navigationBars["User"].exists) + XCTAssertTrue( + app.staticTexts[testEmail].exists, + "The user should be signed in and the email field should display their email." + ) + } + + func testAuthExistingAccountWrongPassword() { + app.staticTexts["Email & Password Login"].tap() + + let testEmail = "test@test.com" + app.textFields["Email"].tap() + app.textFields["Email"].typeText(testEmail) - override func tearDown() { - super.tearDown() - signOut() - } - - func testAuth() { - // Verify that Auth Example app launched successfully - XCTAssertTrue(app.navigationBars["Firebase Auth"].exists) - } + app.textFields["Password"].tap() + app.textFields["Password"].typeText("wrong password") - func testAuthOptions() { - // There are 12 sign in methods, each with its own cell - XCTAssertEqual(app.tables.cells.count, 12) - } + app.buttons["Login"].tap() - func testAuthAnonymously() { - app.staticTexts["Anonymous Authentication"].tap() + wait(forElement: app.alerts.staticTexts["Error"], timeout: 5.0) + XCTAssertTrue(app.alerts.staticTexts["Error"].exists) - wait(forElement: app.navigationBars["User"], timeout: 5.0) - XCTAssertTrue(app.navigationBars["User"].exists) + // Dismiss alert that password was incorrect + app.alerts.buttons["OK"].tap() - let isAnonymousCell = app.cells.containing(.staticText, identifier: "Is User Anonymous?") - .element - XCTAssertTrue(isAnonymousCell.staticTexts["Yes"].exists, "The user should be anonymous") - } + // Go back and check that there is no user that is signed in + app.navigationBars.buttons.firstMatch.tap() + app.tabBars.firstMatch.buttons.element(boundBy: 1).tap() + wait(forElement: app.navigationBars["User"], timeout: 5.0) + XCTAssertEqual( + app.cells.count, + 0, + "The user shouldn't be signed in and the user view should have no cells." + ) + } + + func testCreateAccountBadPassword() { + app.staticTexts["Email & Password Login"].tap() + + let testEmail = "test@test.com" + app.textFields["Email"].tap() + app.textFields["Email"].typeText(testEmail) - func testAuthExistingAccount() { - app.staticTexts["Email & Password Login"].tap() + app.textFields["Password"].tap() + // Enter an invalid password that is "too short" + app.textFields["Password"].typeText("2shrt") - let testEmail = "test@test.com" - app.textFields["Email"].tap() - app.textFields["Email"].typeText(testEmail) - - let testPassword = "test12" - app.textFields["Password"].tap() - app.textFields["Password"].typeText(testPassword) - - app.buttons["Login"].tap() + app.buttons["Create Account"].tap() - wait(forElement: app.navigationBars["User"], timeout: 5.0) - XCTAssertTrue(app.navigationBars["User"].exists) - XCTAssertTrue( - app.staticTexts[testEmail].exists, - "The user should be signed in and the email field should display their email." - ) - } + wait(forElement: app.alerts.staticTexts["Error"], timeout: 5.0) + XCTAssertTrue(app.alerts.staticTexts["Error"].exists) - func testAuthExistingAccountWrongPassword() { - app.staticTexts["Email & Password Login"].tap() + // Dismiss alert that password was incorrect + app.alerts.buttons["OK"].tap() - let testEmail = "test@test.com" - app.textFields["Email"].tap() - app.textFields["Email"].typeText(testEmail) - - app.textFields["Password"].tap() - app.textFields["Password"].typeText("wrong password") - - app.buttons["Login"].tap() - - wait(forElement: app.alerts.staticTexts["Error"], timeout: 5.0) - XCTAssertTrue(app.alerts.staticTexts["Error"].exists) - - // Dismiss alert that password was incorrect - app.alerts.buttons["OK"].tap() - - // Go back and check that there is no user that is signed in - app.navigationBars.buttons.firstMatch.tap() - app.tabBars.firstMatch.buttons.element(boundBy: 1).tap() - wait(forElement: app.navigationBars["User"], timeout: 5.0) - XCTAssertEqual( - app.cells.count, - 0, - "The user shouldn't be signed in and the user view should have no cells." - ) - } - - func testCreateAccountBadPassword() { - app.staticTexts["Email & Password Login"].tap() - - let testEmail = "test@test.com" - app.textFields["Email"].tap() - app.textFields["Email"].typeText(testEmail) - - app.textFields["Password"].tap() - // Enter an invalid password that is "too short" - app.textFields["Password"].typeText("2shrt") - - app.buttons["Create Account"].tap() - - wait(forElement: app.alerts.staticTexts["Error"], timeout: 5.0) - XCTAssertTrue(app.alerts.staticTexts["Error"].exists) - - // Dismiss alert that password was incorrect - app.alerts.buttons["OK"].tap() - - // Go back and check that there is no user that is signed in - app.navigationBars.buttons.firstMatch.tap() - app.tabBars.firstMatch.buttons.element(boundBy: 1).tap() - wait(forElement: app.navigationBars["User"], timeout: 5.0) - XCTAssertEqual( - app.cells.count, - 0, - "The user shouldn't be signed in and the user view should have no cells." - ) - } - - func testCreateAlreadyExistingAccount() { - app.staticTexts["Email & Password Login"].tap() - - let testEmail = "test@test.com" - app.textFields["Email"].tap() - app.textFields["Email"].typeText(testEmail) - - let testPassword = "test12" - app.textFields["Password"].tap() - app.textFields["Password"].typeText(testPassword) - - app.buttons["Create Account"].tap() - - wait(forElement: app.alerts.staticTexts["Error"], timeout: 5.0) - XCTAssertTrue(app.alerts.staticTexts["Error"].exists) - - // Dismiss alert that password was incorrect - app.alerts.buttons["OK"].tap() - - // Go back and check that there is no user that is signed in - app.navigationBars.buttons.firstMatch.tap() - app.tabBars.firstMatch.buttons.element(boundBy: 1).tap() - wait(forElement: app.navigationBars["User"], timeout: 5.0) - XCTAssertEqual( - app.cells.count, - 0, - "The user shouldn't be signed in and the user view should have no cells." - ) - } - - func testCreateAccountCorrectPassword() { - app.staticTexts["Email & Password Login"].tap() - - let newEmail = "\(Date().timeIntervalSince1970)_test@test.com" - app.textFields["Email"].tap() - app.typeText(newEmail) - - let newPassword = "new password" - app.textFields["Password"].tap() - app.typeText(newPassword) - - app.buttons["Create Account"].tap() - - wait(forElement: app.navigationBars["User"], timeout: 5.0) - XCTAssertTrue(app.navigationBars["User"].exists) - XCTAssertTrue( - app.staticTexts[newEmail].exists, - "The user should be signed into the new account." - ) - } - - func DRAFT_testGoogleSignInAndLinkAccount() { - let interruptionMonitor = addUIInterruptionMonitor(withDescription: "Sign in with Google") { - (alert) -> Bool in - alert.buttons["Continue"].tap() - return true + // Go back and check that there is no user that is signed in + app.navigationBars.buttons.firstMatch.tap() + app.tabBars.firstMatch.buttons.element(boundBy: 1).tap() + wait(forElement: app.navigationBars["User"], timeout: 5.0) + XCTAssertEqual( + app.cells.count, + 0, + "The user shouldn't be signed in and the user view should have no cells." + ) } - app.staticTexts["Google"].tap() + func testCreateAlreadyExistingAccount() { + app.staticTexts["Email & Password Login"].tap() + + let testEmail = "test@test.com" + app.textFields["Email"].tap() + app.textFields["Email"].typeText(testEmail) + + let testPassword = "test12" + app.textFields["Password"].tap() + app.textFields["Password"].typeText(testPassword) - app.tap() // Triggers the UIInterruptionMonitor + app.buttons["Create Account"].tap() - let testEmail = "" - let testPassword = "" + wait(forElement: app.alerts.staticTexts["Error"], timeout: 5.0) + XCTAssertTrue(app.alerts.staticTexts["Error"].exists) - let firstTimeLogin = app.webViews.containing(.textField, identifier: "Email or phone") - .element.exists - if firstTimeLogin { - app.webViews.textFields.firstMatch.tap() + // Dismiss alert that password was incorrect + app.alerts.buttons["OK"].tap() - app.webViews.textFields.firstMatch.typeText(testEmail) + // Go back and check that there is no user that is signed in + app.navigationBars.buttons.firstMatch.tap() + app.tabBars.firstMatch.buttons.element(boundBy: 1).tap() + wait(forElement: app.navigationBars["User"], timeout: 5.0) + XCTAssertEqual( + app.cells.count, + 0, + "The user shouldn't be signed in and the user view should have no cells." + ) + } - app.buttons["Done"].tap() // Dismiss keyboard - app.buttons["Next"].tap() // Transition to Google sign in password page + func testCreateAccountCorrectPassword() { + app.staticTexts["Email & Password Login"].tap() - app.webViews.secureTextFields.firstMatch.tap() + let newEmail = "\(Date().timeIntervalSince1970)_test@test.com" + app.textFields["Email"].tap() + app.typeText(newEmail) - app.webViews.secureTextFields.firstMatch.typeText(testPassword) + let newPassword = "new password" + app.textFields["Password"].tap() + app.typeText(newPassword) - app.buttons["Done"].tap() // Dismiss keyboard - app.buttons["Next"].tap() // Complete sign in + app.buttons["Create Account"].tap() - } else { - app.webViews.staticTexts[testEmail].tap() + wait(forElement: app.navigationBars["User"], timeout: 5.0) + XCTAssertTrue(app.navigationBars["User"].exists) + XCTAssertTrue( + app.staticTexts[newEmail].exists, + "The user should be signed into the new account." + ) } - wait(forElement: app.navigationBars["User"], timeout: 5.0) + func DRAFT_testGoogleSignInAndLinkAccount() { + let interruptionMonitor = addUIInterruptionMonitor(withDescription: "Sign in with Google") { + (alert) -> Bool in + alert.buttons["Continue"].tap() + return true + } + + app.staticTexts["Google"].tap() + + app.tap() // Triggers the UIInterruptionMonitor + + let testEmail = "" + let testPassword = "" + + let firstTimeLogin = app.webViews.containing(.textField, identifier: "Email or phone") + .element.exists + if firstTimeLogin { + app.webViews.textFields.firstMatch.tap() - XCTAssertTrue(app.navigationBars["User"].exists) - XCTAssertTrue(app.staticTexts[testEmail].exists) + app.webViews.textFields.firstMatch.typeText(testEmail) - // Cleanup - removeUIInterruptionMonitor(interruptionMonitor) - } + app.buttons["Done"].tap() // Dismiss keyboard + app.buttons["Next"].tap() // Transition to Google sign in password page - func testLaunchPerformance() { - if #available(macOS 10.15, iOS 13.0, tvOS 13.0, *) { - // This measures how long it takes to launch your application. - measure(metrics: [XCTOSSignpostMetric.applicationLaunch]) { - XCUIApplication().launch() - } + app.webViews.secureTextFields.firstMatch.tap() + + app.webViews.secureTextFields.firstMatch.typeText(testPassword) + + app.buttons["Done"].tap() // Dismiss keyboard + app.buttons["Next"].tap() // Complete sign in + + } else { + app.webViews.staticTexts[testEmail].tap() + } + + wait(forElement: app.navigationBars["User"], timeout: 5.0) + + XCTAssertTrue(app.navigationBars["User"].exists) + XCTAssertTrue(app.staticTexts[testEmail].exists) + + // Cleanup + removeUIInterruptionMonitor(interruptionMonitor) + } + + func testLaunchPerformance() { + if #available(macOS 10.15, iOS 13.0, tvOS 13.0, *) { + // This measures how long it takes to launch your application. + measure(metrics: [XCTOSSignpostMetric.applicationLaunch]) { + XCUIApplication().launch() + } + } } - } - // MARK: - Private Helpers + // MARK: - Private Helpers - private func signOut() { - app.tabBars.firstMatch.buttons.element(boundBy: 1).tap() - wait(forElement: app.navigationBars["User"], timeout: 5.0) - if app.staticTexts["Sign Out"].exists { - app.staticTexts["Sign Out"].tap() + private func signOut() { + app.tabBars.firstMatch.buttons.element(boundBy: 1).tap() + wait(forElement: app.navigationBars["User"], timeout: 5.0) + if app.staticTexts["Sign Out"].exists { + app.staticTexts["Sign Out"].tap() + } + app.tabBars.firstMatch.buttons.element(boundBy: 0).tap() } - app.tabBars.firstMatch.buttons.element(boundBy: 0).tap() - } } extension XCTestCase { - func wait(forElement element: XCUIElement, timeout: TimeInterval) { - let predicate = NSPredicate(format: "exists == 1") - expectation(for: predicate, evaluatedWith: element) - waitForExpectations(timeout: timeout) - } + @MainActor + func wait(forElement element: XCUIElement, timeout: TimeInterval) { + let predicate = NSPredicate(format: "exists == 1") + expectation(for: predicate, evaluatedWith: element) + waitForExpectations(timeout: timeout) + } } diff --git a/config/ConfigExample.xcodeproj/project.pbxproj b/config/ConfigExample.xcodeproj/project.pbxproj index 17ada2e54..30296f05d 100644 --- a/config/ConfigExample.xcodeproj/project.pbxproj +++ b/config/ConfigExample.xcodeproj/project.pbxproj @@ -7,9 +7,7 @@ objects = { /* Begin PBXBuildFile section */ - 16F8E38C3F42EB1FE8184C90 /* GoogleService-Info.plist in Sources */ = {isa = PBXBuildFile; fileRef = 45ED9095BE4BD5109A65B35F /* GoogleService-Info.plist */; }; - 3623B61971137623FD028C44 /* GoogleService-Info.plist in Sources */ = {isa = PBXBuildFile; fileRef = 45ED9095BE4BD5109A65B35F /* GoogleService-Info.plist */; }; - 72904828160EC19516620EF7 /* GoogleService-Info.plist in Sources */ = {isa = PBXBuildFile; fileRef = 45ED9095BE4BD5109A65B35F /* GoogleService-Info.plist */; }; + 3623B61971137623FD028C44 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 45ED9095BE4BD5109A65B35F /* GoogleService-Info.plist */; }; 8D00BB6D2D3B07A500F6F6DD /* FirebaseRemoteConfig in Frameworks */ = {isa = PBXBuildFile; productRef = 8D00BB6C2D3B07A500F6F6DD /* FirebaseRemoteConfig */; }; EA062D6024A13966006714D3 /* RemoteConfigView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA062D5F24A13966006714D3 /* RemoteConfigView.swift */; }; EA20B48A2497CC9D00B5E581 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA20B4892497CC9D00B5E581 /* AppDelegate.swift */; }; @@ -278,6 +276,7 @@ EA20B4932497CC9F00B5E581 /* Assets.xcassets in Resources */, EA20B4BA249918F900B5E581 /* salad.json in Resources */, EA20B4C5249959E300B5E581 /* peanut_butter_and_jelly.json in Resources */, + 3623B61971137623FD028C44 /* GoogleService-Info.plist in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -306,7 +305,6 @@ EA20B48A2497CC9D00B5E581 /* AppDelegate.swift in Sources */, EA062D6024A13966006714D3 /* RemoteConfigView.swift in Sources */, EA20B48C2497CC9D00B5E581 /* SceneDelegate.swift in Sources */, - 3623B61971137623FD028C44 /* GoogleService-Info.plist in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -315,7 +313,6 @@ buildActionMask = 2147483647; files = ( EA20B4A12497CC9F00B5E581 /* ConfigExampleTests.swift in Sources */, - 16F8E38C3F42EB1FE8184C90 /* GoogleService-Info.plist in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -324,7 +321,6 @@ buildActionMask = 2147483647; files = ( EA20B4AC2497CC9F00B5E581 /* ConfigExampleUITests.swift in Sources */, - 72904828160EC19516620EF7 /* GoogleService-Info.plist in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -654,7 +650,7 @@ repositoryURL = "https://github.com/firebase/firebase-ios-sdk"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 11.7.0; + minimumVersion = 13.1.1; }; }; /* End XCRemoteSwiftPackageReference section */ diff --git a/crashlytics/CrashlyticsExample.xcodeproj/project.pbxproj b/crashlytics/CrashlyticsExample.xcodeproj/project.pbxproj index c0276a557..d7844eca1 100644 --- a/crashlytics/CrashlyticsExample.xcodeproj/project.pbxproj +++ b/crashlytics/CrashlyticsExample.xcodeproj/project.pbxproj @@ -1625,7 +1625,7 @@ repositoryURL = "https://github.com/firebase/firebase-ios-sdk"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 11.7.0; + minimumVersion = 13.1.1; }; }; /* End XCRemoteSwiftPackageReference section */ diff --git a/database/DatabaseExample.xcodeproj/project.pbxproj b/database/DatabaseExample.xcodeproj/project.pbxproj index 8d5566407..ff3297e0c 100644 --- a/database/DatabaseExample.xcodeproj/project.pbxproj +++ b/database/DatabaseExample.xcodeproj/project.pbxproj @@ -22,7 +22,7 @@ 10B872C21CDC0AC200C7D214 /* PostDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 10B872C11CDC0AC200C7D214 /* PostDataSource.swift */; }; 10B872C41CDC11F200C7D214 /* RecentPostsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 10B872C31CDC11F200C7D214 /* RecentPostsViewController.swift */; }; 10B872C61CDC14C800C7D214 /* MyTopPostsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 10B872C51CDC14C800C7D214 /* MyTopPostsViewController.swift */; }; - 4022DB8C90CB216288A52649 /* GoogleService-Info.plist in Sources */ = {isa = PBXBuildFile; fileRef = F644C72A588FB44DD666857B /* GoogleService-Info.plist */; }; + 4022DB8C90CB216288A52649 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = F644C72A588FB44DD666857B /* GoogleService-Info.plist */; }; 5F5A537E1ADE67D500F81DF0 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F5A537D1ADE67D500F81DF0 /* AppDelegate.swift */; }; 5F5A53801ADE67D500F81DF0 /* NewPostViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F5A537F1ADE67D500F81DF0 /* NewPostViewController.swift */; }; 5F5A539C1ADE69AA00F81DF0 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5F5A53591ADE670C00F81DF0 /* Main.storyboard */; }; @@ -31,7 +31,6 @@ 8D00BB822D3B0C1F00F6F6DD /* FirebaseAuth in Frameworks */ = {isa = PBXBuildFile; productRef = 8D00BB812D3B0C1F00F6F6DD /* FirebaseAuth */; }; 8D00BB842D3B0C1F00F6F6DD /* FirebaseDatabase in Frameworks */ = {isa = PBXBuildFile; productRef = 8D00BB832D3B0C1F00F6F6DD /* FirebaseDatabase */; }; 8D00BB862D3B0C1F00F6F6DD /* FirebaseDatabaseUI in Frameworks */ = {isa = PBXBuildFile; productRef = 8D00BB852D3B0C1F00F6F6DD /* FirebaseDatabaseUI */; }; - A18EF550576A3F1E297EFB56 /* GoogleService-Info.plist in Sources */ = {isa = PBXBuildFile; fileRef = F644C72A588FB44DD666857B /* GoogleService-Info.plist */; }; DEC82E5523AD38E4000EA7B1 /* FIREGSignInHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = DEC82E5223AD38E4000EA7B1 /* FIREGSignInHelper.m */; }; DEC82E5623AD38E4000EA7B1 /* FIREGHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = DEC82E5323AD38E4000EA7B1 /* FIREGHelper.m */; }; EF94DD7B1CE533AC009A635D /* PostTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = EFDCBD531CE4FCC800F68D48 /* PostTableViewCell.xib */; }; @@ -316,6 +315,8 @@ 5F5A539C1ADE69AA00F81DF0 /* Main.storyboard in Resources */, 5F99610B1AE0CF4F0034F503 /* Images.xcassets in Resources */, EF94DD7B1CE533AC009A635D /* PostTableViewCell.xib in Resources */, + + 4022DB8C90CB216288A52649 /* GoogleService-Info.plist in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -329,7 +330,6 @@ 1073483720333B8C004A66D1 /* DatabaseExampleUITests.m in Sources */, DEC82E5523AD38E4000EA7B1 /* FIREGSignInHelper.m in Sources */, DEC82E5623AD38E4000EA7B1 /* FIREGHelper.m in Sources */, - A18EF550576A3F1E297EFB56 /* GoogleService-Info.plist in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -352,7 +352,6 @@ 10B872AE1CDBFB1F00C7D214 /* MyPostsViewController.swift in Sources */, 1073FE101CE2D27700E2F73F /* TabBarController.swift in Sources */, 10B872B91CDC056B00C7D214 /* User.swift in Sources */, - 4022DB8C90CB216288A52649 /* GoogleService-Info.plist in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -640,7 +639,7 @@ repositoryURL = "https://github.com/firebase/firebase-ios-sdk"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 11.7.0; + minimumVersion = 13.1.1; }; }; 8D00BB7E2D3B0C0200F6F6DD /* XCRemoteSwiftPackageReference "FirebaseUI-iOS" */ = { @@ -648,7 +647,7 @@ repositoryURL = "https://github.com/firebase/FirebaseUI-iOS"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 16.0.0; + minimumVersion = 16.2.0; }; }; /* End XCRemoteSwiftPackageReference section */ diff --git a/database/DatabaseExampleSwiftUI/DatabaseExample/DatabaseExample.xcodeproj/project.pbxproj b/database/DatabaseExampleSwiftUI/DatabaseExample/DatabaseExample.xcodeproj/project.pbxproj index 9582a4eeb..4ac44eb0c 100644 --- a/database/DatabaseExampleSwiftUI/DatabaseExample/DatabaseExample.xcodeproj/project.pbxproj +++ b/database/DatabaseExampleSwiftUI/DatabaseExample/DatabaseExample.xcodeproj/project.pbxproj @@ -68,9 +68,6 @@ C7F60CF7268EBEDE002F68AB /* UserViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7F60CF5268EBEDE002F68AB /* UserViewModel.swift */; }; C7F60CFC268EE316002F68AB /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7F60CFB268EE316002F68AB /* ContentView.swift */; }; C7F60CFD268EE316002F68AB /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7F60CFB268EE316002F68AB /* ContentView.swift */; }; - DEDAD221282B281F00DEC2CA /* FirebaseDatabaseSwift in Frameworks */ = {isa = PBXBuildFile; productRef = DEDAD220282B281F00DEC2CA /* FirebaseDatabaseSwift */; }; - DEDAD223282B282F00DEC2CA /* FirebaseDatabaseSwift in Frameworks */ = {isa = PBXBuildFile; productRef = DEDAD222282B282F00DEC2CA /* FirebaseDatabaseSwift */; }; - DEDAD225282B283600DEC2CA /* FirebaseDatabaseSwift in Frameworks */ = {isa = PBXBuildFile; productRef = DEDAD224282B283600DEC2CA /* FirebaseDatabaseSwift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -155,7 +152,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - DEDAD223282B282F00DEC2CA /* FirebaseDatabaseSwift in Frameworks */, C796F2C526D228F30076C5F5 /* FirebaseDatabase in Frameworks */, C796F2C326D228F30076C5F5 /* FirebaseAuth in Frameworks */, ); @@ -165,7 +161,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - DEDAD221282B281F00DEC2CA /* FirebaseDatabaseSwift in Frameworks */, C7C6B59B268C8D7800546A57 /* FirebaseDatabase in Frameworks */, C7C6B599268C8D7800546A57 /* FirebaseAuth in Frameworks */, ); @@ -175,7 +170,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - DEDAD225282B283600DEC2CA /* FirebaseDatabaseSwift in Frameworks */, C796F29A26CCDCF50076C5F5 /* FirebaseDatabase in Frameworks */, C796F29826CCDCEF0076C5F5 /* FirebaseAuth in Frameworks */, ); @@ -364,7 +358,6 @@ packageProductDependencies = ( C796F2C226D228F30076C5F5 /* FirebaseAuth */, C796F2C426D228F30076C5F5 /* FirebaseDatabase */, - DEDAD222282B282F00DEC2CA /* FirebaseDatabaseSwift */, ); productName = tvOSDatabaseExample; productReference = C796F2A326D228180076C5F5 /* DatabaseExample (tvOS).app */; @@ -386,7 +379,6 @@ packageProductDependencies = ( C7C6B598268C8D7800546A57 /* FirebaseAuth */, C7C6B59A268C8D7800546A57 /* FirebaseDatabase */, - DEDAD220282B281F00DEC2CA /* FirebaseDatabaseSwift */, ); productName = "DatabaseExample (iOS)"; productReference = C7C6B57B268C8C9C00546A57 /* DatabaseExample.app */; @@ -408,7 +400,6 @@ packageProductDependencies = ( C796F29726CCDCEF0076C5F5 /* FirebaseAuth */, C796F29926CCDCF50076C5F5 /* FirebaseDatabase */, - DEDAD224282B283600DEC2CA /* FirebaseDatabaseSwift */, ); productName = "DatabaseExample (macOS)"; productReference = C7C6B583268C8C9C00546A57 /* DatabaseExample.app */; @@ -1117,7 +1108,7 @@ repositoryURL = "https://github.com/firebase/firebase-ios-sdk.git"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 9.0.0; + minimumVersion = 13.1.1; }; }; /* End XCRemoteSwiftPackageReference section */ @@ -1153,21 +1144,6 @@ package = C7C6B595268C8D7800546A57 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; productName = FirebaseDatabase; }; - DEDAD220282B281F00DEC2CA /* FirebaseDatabaseSwift */ = { - isa = XCSwiftPackageProductDependency; - package = C7C6B595268C8D7800546A57 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; - productName = FirebaseDatabaseSwift; - }; - DEDAD222282B282F00DEC2CA /* FirebaseDatabaseSwift */ = { - isa = XCSwiftPackageProductDependency; - package = C7C6B595268C8D7800546A57 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; - productName = FirebaseDatabaseSwift; - }; - DEDAD224282B283600DEC2CA /* FirebaseDatabaseSwift */ = { - isa = XCSwiftPackageProductDependency; - package = C7C6B595268C8D7800546A57 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; - productName = FirebaseDatabaseSwift; - }; /* End XCSwiftPackageProductDependency section */ }; rootObject = C7C6B56F268C8C9A00546A57 /* Project object */; diff --git a/firebaseai/FirebaseAIExample.xcodeproj/project.pbxproj b/firebaseai/FirebaseAIExample.xcodeproj/project.pbxproj index 6ca5b556b..861614ad3 100644 --- a/firebaseai/FirebaseAIExample.xcodeproj/project.pbxproj +++ b/firebaseai/FirebaseAIExample.xcodeproj/project.pbxproj @@ -373,8 +373,8 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/firebase/firebase-ios-sdk"; requirement = { - branch = "CocoaPods-13.0.0"; - kind = branch; + kind = upToNextMajorVersion; + minimumVersion = 13.1.1; }; traits = ( ); diff --git a/firestore/FirestoreExample.xcodeproj/project.pbxproj b/firestore/FirestoreExample.xcodeproj/project.pbxproj index 6a190a31e..18bb91c56 100644 --- a/firestore/FirestoreExample.xcodeproj/project.pbxproj +++ b/firestore/FirestoreExample.xcodeproj/project.pbxproj @@ -7,9 +7,8 @@ objects = { /* Begin PBXBuildFile section */ - 0F5846D1C922E1CBB0D0B628 /* GoogleService-Info.plist in Sources */ = {isa = PBXBuildFile; fileRef = 6AA80A371E484FE3095D24C4 /* GoogleService-Info.plist */; }; + 0F5846D1C922E1CBB0D0B628 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 6AA80A371E484FE3095D24C4 /* GoogleService-Info.plist */; }; 10734773203159E4004A66D1 /* FirestoreExampleUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 10734772203159E4004A66D1 /* FirestoreExampleUITests.swift */; }; - 1A8221B8C73DEF29AF54187B /* GoogleService-Info.plist in Sources */ = {isa = PBXBuildFile; fileRef = 6AA80A371E484FE3095D24C4 /* GoogleService-Info.plist */; }; 8670CB2C286A218500070BC6 /* UINavigationBar+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8670CB2B286A218500070BC6 /* UINavigationBar+Extension.swift */; }; 8D004D2D1EE879B5004DEF35 /* FiltersViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D004D2C1EE879B5004DEF35 /* FiltersViewController.swift */; }; 8D5DF7021F7EF3BA00B0D24F /* NewReviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D5DF7011F7EF3BA00B0D24F /* NewReviewViewController.swift */; }; @@ -21,8 +20,6 @@ 8D9BBC461EE2200900194E9A /* FirestoreExampleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D9BBC451EE2200900194E9A /* FirestoreExampleTests.swift */; }; 8DC73D692D41A3AD0092B6EE /* FirebaseAuth in Frameworks */ = {isa = PBXBuildFile; productRef = 8DC73D682D41A3AD0092B6EE /* FirebaseAuth */; }; 8DC73D6B2D41A3AD0092B6EE /* FirebaseFirestore in Frameworks */ = {isa = PBXBuildFile; productRef = 8DC73D6A2D41A3AD0092B6EE /* FirebaseFirestore */; }; - 8DC73D6D2D41A3B60092B6EE /* FirebaseAuthUI in Frameworks */ = {isa = PBXBuildFile; productRef = 8DC73D6C2D41A3B60092B6EE /* FirebaseAuthUI */; }; - 8DC73D6F2D41A3B60092B6EE /* FirebaseEmailAuthUI in Frameworks */ = {isa = PBXBuildFile; productRef = 8DC73D6E2D41A3B60092B6EE /* FirebaseEmailAuthUI */; }; 8DC73D722D41A4330092B6EE /* SDWebImage in Frameworks */ = {isa = PBXBuildFile; productRef = 8DC73D712D41A4330092B6EE /* SDWebImage */; }; 8DC73D742D41A5BF0092B6EE /* FirebaseAuth in Frameworks */ = {isa = PBXBuildFile; productRef = 8DC73D732D41A5BF0092B6EE /* FirebaseAuth */; }; 8DC73D762D41A5BF0092B6EE /* FirebaseFirestore in Frameworks */ = {isa = PBXBuildFile; productRef = 8DC73D752D41A5BF0092B6EE /* FirebaseFirestore */; }; @@ -51,8 +48,7 @@ 8E919E80260D3956007C62C4 /* RestaurantDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E919E7D260D3956007C62C4 /* RestaurantDetailView.swift */; }; 8E919E81260D3956007C62C4 /* ReviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E919E7E260D3956007C62C4 /* ReviewView.swift */; }; 8EB1D03F2633838500F8AD8E /* SignInView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8EB1D03E2633838500F8AD8E /* SignInView.swift */; }; - DA3C6D5CCAE3BF02ED7A97A6 /* GoogleService-Info.plist in Sources */ = {isa = PBXBuildFile; fileRef = 6AA80A371E484FE3095D24C4 /* GoogleService-Info.plist */; }; - DA5D6E6B3E69955807917A8E /* GoogleService-Info.plist in Sources */ = {isa = PBXBuildFile; fileRef = 6AA80A371E484FE3095D24C4 /* GoogleService-Info.plist */; }; + DA3C6D5CCAE3BF02ED7A97A6 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 6AA80A371E484FE3095D24C4 /* GoogleService-Info.plist */; }; DE8564AE23AFBF8000611383 /* FirestoreUITest.m in Sources */ = {isa = PBXBuildFile; fileRef = DE8564AD23AFBF8000611383 /* FirestoreUITest.m */; }; DE8564B423AFBFA700611383 /* FIREGSignInHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = DE8564B123AFBFA700611383 /* FIREGSignInHelper.m */; }; DE8564B523AFBFA700611383 /* FIREGHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = DE8564B223AFBFA700611383 /* FIREGHelper.m */; }; @@ -141,9 +137,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 8DC73D6F2D41A3B60092B6EE /* FirebaseEmailAuthUI in Frameworks */, 8DC73D692D41A3AD0092B6EE /* FirebaseAuth in Frameworks */, - 8DC73D6D2D41A3B60092B6EE /* FirebaseAuthUI in Frameworks */, 8DC73D6B2D41A3AD0092B6EE /* FirebaseFirestore in Frameworks */, 8DC73D722D41A4330092B6EE /* SDWebImage in Frameworks */, ); @@ -439,7 +433,6 @@ mainGroup = 8D9BBC241EE2200800194E9A; packageReferences = ( 8DC73D652D41A3040092B6EE /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */, - 8DC73D662D41A3410092B6EE /* XCRemoteSwiftPackageReference "FirebaseUI-iOS" */, 8DC73D702D41A42A0092B6EE /* XCRemoteSwiftPackageReference "SDWebImage" */, ); preferredProjectObjectVersion = 77; @@ -471,6 +464,7 @@ 8D9BBC381EE2200900194E9A /* Assets.xcassets in Resources */, 8D9BBC361EE2200900194E9A /* Main.storyboard in Resources */, 8DD9ACDA1F7B166900C2DD24 /* pizza-monster.png in Resources */, + DA3C6D5CCAE3BF02ED7A97A6 /* GoogleService-Info.plist in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -487,6 +481,7 @@ files = ( 8E4C62D825E9CFE1001678A1 /* Preview Assets.xcassets in Resources */, 8E4C62D525E9CFE1001678A1 /* Assets.xcassets in Resources */, + 0F5846D1C922E1CBB0D0B628 /* GoogleService-Info.plist in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -501,7 +496,6 @@ DE8564B523AFBFA700611383 /* FIREGHelper.m in Sources */, DE8564AE23AFBF8000611383 /* FirestoreUITest.m in Sources */, DE8564B423AFBFA700611383 /* FIREGSignInHelper.m in Sources */, - 1A8221B8C73DEF29AF54187B /* GoogleService-Info.plist in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -518,7 +512,6 @@ 8DF77E1A1EEB45E500CB2330 /* RestaurantDetailViewController.swift in Sources */, 8D004D2D1EE879B5004DEF35 /* FiltersViewController.swift in Sources */, 8DF77E1C1EEB645100CB2330 /* StarsView.swift in Sources */, - DA3C6D5CCAE3BF02ED7A97A6 /* GoogleService-Info.plist in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -527,7 +520,6 @@ buildActionMask = 2147483647; files = ( 8D9BBC461EE2200900194E9A /* FirestoreExampleTests.swift in Sources */, - DA5D6E6B3E69955807917A8E /* GoogleService-Info.plist in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -553,7 +545,6 @@ 8E919E71260D3945007C62C4 /* RestaurantViewModel.swift in Sources */, 8E4C62E725E9D191001678A1 /* RestaurantItemView.swift in Sources */, 8E4C63D825F6D641001678A1 /* Firestore+Extension.swift in Sources */, - 0F5846D1C922E1CBB0D0B628 /* GoogleService-Info.plist in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -957,15 +948,7 @@ repositoryURL = "https://github.com/firebase/firebase-ios-sdk"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 11.7.0; - }; - }; - 8DC73D662D41A3410092B6EE /* XCRemoteSwiftPackageReference "FirebaseUI-iOS" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/firebase/FirebaseUI-iOS"; - requirement = { - kind = upToNextMajorVersion; - minimumVersion = 15.0.0; + minimumVersion = 13.1.1; }; }; 8DC73D702D41A42A0092B6EE /* XCRemoteSwiftPackageReference "SDWebImage" */ = { @@ -989,16 +972,6 @@ package = 8DC73D652D41A3040092B6EE /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; productName = FirebaseFirestore; }; - 8DC73D6C2D41A3B60092B6EE /* FirebaseAuthUI */ = { - isa = XCSwiftPackageProductDependency; - package = 8DC73D662D41A3410092B6EE /* XCRemoteSwiftPackageReference "FirebaseUI-iOS" */; - productName = FirebaseAuthUI; - }; - 8DC73D6E2D41A3B60092B6EE /* FirebaseEmailAuthUI */ = { - isa = XCSwiftPackageProductDependency; - package = 8DC73D662D41A3410092B6EE /* XCRemoteSwiftPackageReference "FirebaseUI-iOS" */; - productName = FirebaseEmailAuthUI; - }; 8DC73D712D41A4330092B6EE /* SDWebImage */ = { isa = XCSwiftPackageProductDependency; package = 8DC73D702D41A42A0092B6EE /* XCRemoteSwiftPackageReference "SDWebImage" */; diff --git a/firestore/FirestoreExample/RestaurantsTableViewController.swift b/firestore/FirestoreExample/RestaurantsTableViewController.swift index 460b97735..da8d23674 100644 --- a/firestore/FirestoreExample/RestaurantsTableViewController.swift +++ b/firestore/FirestoreExample/RestaurantsTableViewController.swift @@ -14,355 +14,351 @@ // limitations under the License. // -import UIKit -import FirebaseFirestore import FirebaseAuth -import FirebaseAuthUI -import FirebaseEmailAuthUI +import FirebaseFirestore +import UIKit func priceString(from price: Int) -> String { - let priceText: String - switch price { - case 1: - priceText = "$" - case 2: - priceText = "$$" - case 3: - priceText = "$$$" - case _: - fatalError("price must be between one and three") - } - - return priceText + let priceText: String + switch price { + case 1: + priceText = "$" + case 2: + priceText = "$$" + case 3: + priceText = "$$$" + case _: + fatalError("price must be between one and three") + } + + return priceText } class RestaurantsTableViewController: UIViewController, UITableViewDataSource, UITableViewDelegate { - @IBOutlet var tableView: UITableView! - @IBOutlet var activeFiltersStackView: UIStackView! - @IBOutlet var stackViewHeightConstraint: NSLayoutConstraint! + @IBOutlet var tableView: UITableView! + @IBOutlet var activeFiltersStackView: UIStackView! + @IBOutlet var stackViewHeightConstraint: NSLayoutConstraint! - @IBOutlet var cityFilterLabel: UILabel! - @IBOutlet var categoryFilterLabel: UILabel! - @IBOutlet var priceFilterLabel: UILabel! + @IBOutlet var cityFilterLabel: UILabel! + @IBOutlet var categoryFilterLabel: UILabel! + @IBOutlet var priceFilterLabel: UILabel! - let backgroundView = UIImageView() + let backgroundView = UIImageView() - private var restaurants: [Restaurant] = [] - private var documents: [DocumentSnapshot] = [] + private var restaurants: [Restaurant] = [] + private var documents: [DocumentSnapshot] = [] - fileprivate var query: Query? { - didSet { - if let listener = listener { - listener.remove() - observeQuery() - } - } - } - - private var listener: ListenerRegistration? - - fileprivate func observeQuery() { - guard let query = query else { return } - stopObserving() - - // Display data from Firestore, part one - - listener = query.addSnapshotListener { [unowned self] snapshot, error in - guard let snapshot = snapshot else { - print("Error fetching snapshot results: \(error!)") - return - } - let models = snapshot.documents.map { (document) -> Restaurant in - let maybeModel: Restaurant? - do { - maybeModel = try document.data(as: Restaurant.self) - } catch { - fatalError( - "Unable to initialize type \(Restaurant.self) with dictionary \(document.data()): \(error)" - ) + fileprivate var query: Query? { + didSet { + if let listener = listener { + listener.remove() + observeQuery() + } } + } - if let model = maybeModel { - return model - } else { - // Don't use fatalError here in a real app. - fatalError("Missing document of type \(Restaurant.self) at \(document.reference.path)") + private var listener: ListenerRegistration? + + fileprivate func observeQuery() { + guard let query = query else { return } + stopObserving() + + // Display data from Firestore, part one + + listener = query.addSnapshotListener { [unowned self] snapshot, error in + guard let snapshot = snapshot else { + print("Error fetching snapshot results: \(error!)") + return + } + let models = snapshot.documents.map { (document) -> Restaurant in + let maybeModel: Restaurant? + do { + maybeModel = try document.data(as: Restaurant.self) + } catch { + fatalError( + "Unable to initialize type \(Restaurant.self) with dictionary \(document.data()): \(error)" + ) + } + + if let model = maybeModel { + return model + } else { + // Don't use fatalError here in a real app. + fatalError("Missing document of type \(Restaurant.self) at \(document.reference.path)") + } + } + self.restaurants = models + self.documents = snapshot.documents + + if self.documents.count > 0 { + self.tableView.backgroundView = nil + } else { + self.tableView.backgroundView = self.backgroundView + } + + self.tableView.reloadData() } - } - self.restaurants = models - self.documents = snapshot.documents + } - if self.documents.count > 0 { - self.tableView.backgroundView = nil - } else { - self.tableView.backgroundView = self.backgroundView - } + fileprivate func stopObserving() { + listener?.remove() + } - self.tableView.reloadData() + fileprivate func baseQuery() -> Query { + return Firestore.firestore().collection("restaurants").limit(to: 50) } - } - - fileprivate func stopObserving() { - listener?.remove() - } - - fileprivate func baseQuery() -> Query { - return Firestore.firestore().collection("restaurants").limit(to: 50) - } - - private lazy var filters: (navigationController: UINavigationController, - filtersController: FiltersViewController) = { - FiltersViewController.fromStoryboard(delegate: self) - }() - - override func viewDidLoad() { - super.viewDidLoad() - backgroundView.image = UIImage(named: "pizza-monster")! - backgroundView.contentMode = .scaleAspectFit - backgroundView.alpha = 0.5 - tableView.backgroundView = backgroundView - tableView.tableFooterView = UIView() - - navigationController?.navigationBar.applyFirebaseAppearance() - - tableView.dataSource = self - tableView.delegate = self - query = baseQuery() - stackViewHeightConstraint.constant = 0 - activeFiltersStackView.isHidden = true - } - - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - setNeedsStatusBarAppearanceUpdate() - observeQuery() - } - - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - let auth = FUIAuth.defaultAuthUI()! - let underlyingAuth = Auth.auth() - if underlyingAuth.currentUser == nil { - let emailAuthProvider = FUIEmailAuth() - auth.providers = [emailAuthProvider] - present(auth.authViewController(), animated: true, completion: nil) + + private lazy var filters: (navigationController: UINavigationController, + filtersController: FiltersViewController) = { + FiltersViewController.fromStoryboard(delegate: self) + }() + + override func viewDidLoad() { + super.viewDidLoad() + backgroundView.image = UIImage(named: "pizza-monster")! + backgroundView.contentMode = .scaleAspectFit + backgroundView.alpha = 0.5 + tableView.backgroundView = backgroundView + tableView.tableFooterView = UIView() + + navigationController?.navigationBar.applyFirebaseAppearance() + + tableView.dataSource = self + tableView.delegate = self + query = baseQuery() + stackViewHeightConstraint.constant = 0 + activeFiltersStackView.isHidden = true } - } - - override func viewWillDisappear(_ animated: Bool) { - super.viewWillDisappear(animated) - stopObserving() - } - - @IBAction func didTapPopulateButton(_ sender: Any) { - let words = ["Bar", "Fire", "Grill", "Drive Thru", "Place", "Best", "Spot", "Prime", "Eatin'"] - - let cities = Restaurant.cities - let categories = Restaurant.categories - - for _ in 0 ..< 20 { - let randomIndexes = (Int(arc4random_uniform(UInt32(words.count))), - Int(arc4random_uniform(UInt32(words.count)))) - let name = words[randomIndexes.0] + " " + words[randomIndexes.1] - let category = categories[Int(arc4random_uniform(UInt32(categories.count)))] - let city = cities[Int(arc4random_uniform(UInt32(cities.count)))] - let price = Int(arc4random_uniform(3)) + 1 - let photo = Restaurant.imageURL(forName: name) - - // Basic writes - - let collection = Firestore.firestore().collection("restaurants") - - let restaurant = Restaurant( - name: name, - category: category, - city: city, - price: price, - ratingCount: 10, - averageRating: 0, - photo: photo - ) - - let restaurantRef = collection.document() - do { - try restaurantRef.setData(from: restaurant) - } catch { - fatalError("Encoding Restaurant failed: \(error)") - } - - let batch = Firestore.firestore().batch() - guard let user = Auth.auth().currentUser else { continue } - var average: Float = 0 - for _ in 0 ..< 10 { - let rating = Int(arc4random_uniform(5) + 1) - average += Float(rating) / 10 - let text = rating > 3 ? "good" : "food was too spicy" - let review = Review(rating: rating, - userID: user.uid, - username: user.displayName ?? "Anonymous", - text: text, - date: Timestamp()) - let ratingRef = restaurantRef.collection("ratings").document() - do { - try batch.setData(from: review, forDocument: ratingRef) - } catch { - fatalError("Encoding Rating failed: \(error)") + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + setNeedsStatusBarAppearanceUpdate() + observeQuery() + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + let underlyingAuth = Auth.auth() + if underlyingAuth.currentUser == nil { + underlyingAuth.signInAnonymously() } - } - batch.updateData(["avgRating": average], forDocument: restaurantRef) - batch.commit(completion: { error in - guard let error = error else { return } - print("Error generating reviews: \(error). Check your Firestore permissions.") - }) } - } - - @IBAction func didTapClearButton(_ sender: Any) { - filters.filtersController.clearFilters() - controller( - filters.filtersController, - didSelectCategory: nil, - city: nil, - price: nil, - sortBy: nil - ) - } - - @IBAction func didTapFilterButton(_ sender: Any) { - present(filters.navigationController, animated: true, completion: nil) - } - - override var preferredStatusBarStyle: UIStatusBarStyle { - set {} - get { - return .lightContent + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + stopObserving() } - } - - deinit { - listener?.remove() - } - - // MARK: - UITableViewDataSource - - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: "RestaurantTableViewCell", - for: indexPath) as! RestaurantTableViewCell - let restaurant = restaurants[indexPath.row] - cell.populate(restaurant: restaurant) - return cell - } - - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return restaurants.count - } - - // MARK: - UITableViewDelegate - - func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - tableView.deselectRow(at: indexPath, animated: true) - let controller = RestaurantDetailViewController.fromStoryboard() - controller.titleImageURL = restaurants[indexPath.row].photo - controller.restaurant = restaurants[indexPath.row] - controller.restaurantReference = documents[indexPath.row].reference - navigationController?.pushViewController(controller, animated: true) - } -} -extension RestaurantsTableViewController: FiltersViewControllerDelegate { - func query(withCategory category: String?, city: String?, price: Int?, sortBy: String?) -> Query { - var filtered = baseQuery() - - if category == nil, city == nil, price == nil, sortBy == nil { - stackViewHeightConstraint.constant = 0 - activeFiltersStackView.isHidden = true - } else { - stackViewHeightConstraint.constant = 44 - activeFiltersStackView.isHidden = false + @IBAction func didTapPopulateButton(_: Any) { + let words = ["Bar", "Fire", "Grill", "Drive Thru", "Place", "Best", "Spot", "Prime", "Eatin'"] + + let cities = Restaurant.cities + let categories = Restaurant.categories + + for _ in 0 ..< 20 { + let randomIndexes = (Int(arc4random_uniform(UInt32(words.count))), + Int(arc4random_uniform(UInt32(words.count)))) + let name = words[randomIndexes.0] + " " + words[randomIndexes.1] + let category = categories[Int(arc4random_uniform(UInt32(categories.count)))] + let city = cities[Int(arc4random_uniform(UInt32(cities.count)))] + let price = Int(arc4random_uniform(3)) + 1 + let photo = Restaurant.imageURL(forName: name) + + // Basic writes + + let collection = Firestore.firestore().collection("restaurants") + + let restaurant = Restaurant( + name: name, + category: category, + city: city, + price: price, + ratingCount: 10, + averageRating: 0, + photo: photo + ) + + let restaurantRef = collection.document() + do { + try restaurantRef.setData(from: restaurant) + } catch { + fatalError("Encoding Restaurant failed: \(error)") + } + + let batch = Firestore.firestore().batch() + guard let user = Auth.auth().currentUser else { continue } + var average: Float = 0 + for _ in 0 ..< 10 { + let rating = Int(arc4random_uniform(5) + 1) + average += Float(rating) / 10 + let text = rating > 3 ? "good" : "food was too spicy" + let review = Review(rating: rating, + userID: user.uid, + username: user.displayName ?? "Anonymous", + text: text, + date: Timestamp()) + let ratingRef = restaurantRef.collection("ratings").document() + do { + try batch.setData(from: review, forDocument: ratingRef) + } catch { + fatalError("Encoding Rating failed: \(error)") + } + } + batch.updateData(["avgRating": average], forDocument: restaurantRef) + batch.commit(completion: { error in + guard let error = error else { return } + print("Error generating reviews: \(error). Check your Firestore permissions.") + }) + } } - // Advanced queries + @IBAction func didTapClearButton(_: Any) { + filters.filtersController.clearFilters() + controller( + filters.filtersController, + didSelectCategory: nil, + city: nil, + price: nil, + sortBy: nil + ) + } - if let category = category, !category.isEmpty { - filtered = filtered.whereField("category", isEqualTo: category) + @IBAction func didTapFilterButton(_: Any) { + present(filters.navigationController, animated: true, completion: nil) } - if let city = city, !city.isEmpty { - filtered = filtered.whereField("city", isEqualTo: city) + override var preferredStatusBarStyle: UIStatusBarStyle { + set {} + get { + return .lightContent + } } - if let price = price { - filtered = filtered.whereField("price", isEqualTo: price) + deinit { + listener?.remove() } - if let sortBy = sortBy, !sortBy.isEmpty { - filtered = filtered.order(by: sortBy) + // MARK: - UITableViewDataSource + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "RestaurantTableViewCell", + for: indexPath) as! RestaurantTableViewCell + let restaurant = restaurants[indexPath.row] + cell.populate(restaurant: restaurant) + return cell } - return filtered - } - - func controller(_ controller: FiltersViewController, - didSelectCategory category: String?, - city: String?, - price: Int?, - sortBy: String?) { - let filtered = query(withCategory: category, city: city, price: price, sortBy: sortBy) - - if let category = category, !category.isEmpty { - categoryFilterLabel.text = category - categoryFilterLabel.isHidden = false - } else { - categoryFilterLabel.isHidden = true + func tableView(_: UITableView, numberOfRowsInSection _: Int) -> Int { + return restaurants.count } - if let city = city, !city.isEmpty { - cityFilterLabel.text = city - cityFilterLabel.isHidden = false - } else { - cityFilterLabel.isHidden = true + // MARK: - UITableViewDelegate + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) + let controller = RestaurantDetailViewController.fromStoryboard() + controller.titleImageURL = restaurants[indexPath.row].photo + controller.restaurant = restaurants[indexPath.row] + controller.restaurantReference = documents[indexPath.row].reference + navigationController?.pushViewController(controller, animated: true) } +} - if let price = price { - priceFilterLabel.text = priceString(from: price) - priceFilterLabel.isHidden = false - } else { - priceFilterLabel.isHidden = true +extension RestaurantsTableViewController: FiltersViewControllerDelegate { + func query(withCategory category: String?, city: String?, price: Int?, sortBy: String?) -> Query { + var filtered = baseQuery() + + if category == nil, city == nil, price == nil, sortBy == nil { + stackViewHeightConstraint.constant = 0 + activeFiltersStackView.isHidden = true + } else { + stackViewHeightConstraint.constant = 44 + activeFiltersStackView.isHidden = false + } + + // Advanced queries + + if let category = category, !category.isEmpty { + filtered = filtered.whereField("category", isEqualTo: category) + } + + if let city = city, !city.isEmpty { + filtered = filtered.whereField("city", isEqualTo: city) + } + + if let price = price { + filtered = filtered.whereField("price", isEqualTo: price) + } + + if let sortBy = sortBy, !sortBy.isEmpty { + filtered = filtered.order(by: sortBy) + } + + return filtered } - query = filtered - observeQuery() - } + func controller(_: FiltersViewController, + didSelectCategory category: String?, + city: String?, + price: Int?, + sortBy: String?) + { + let filtered = query(withCategory: category, city: city, price: price, sortBy: sortBy) + + if let category = category, !category.isEmpty { + categoryFilterLabel.text = category + categoryFilterLabel.isHidden = false + } else { + categoryFilterLabel.isHidden = true + } + + if let city = city, !city.isEmpty { + cityFilterLabel.text = city + cityFilterLabel.isHidden = false + } else { + cityFilterLabel.isHidden = true + } + + if let price = price { + priceFilterLabel.text = priceString(from: price) + priceFilterLabel.isHidden = false + } else { + priceFilterLabel.isHidden = true + } + + query = filtered + observeQuery() + } } class RestaurantTableViewCell: UITableViewCell { - @IBOutlet private var thumbnailView: UIImageView! + @IBOutlet private var thumbnailView: UIImageView! - @IBOutlet private var nameLabel: UILabel! + @IBOutlet private var nameLabel: UILabel! - @IBOutlet var starsView: ImmutableStarsView! + @IBOutlet var starsView: ImmutableStarsView! - @IBOutlet private var cityLabel: UILabel! + @IBOutlet private var cityLabel: UILabel! - @IBOutlet private var categoryLabel: UILabel! + @IBOutlet private var categoryLabel: UILabel! - @IBOutlet private var priceLabel: UILabel! + @IBOutlet private var priceLabel: UILabel! - func populate(restaurant: Restaurant) { - // Displaying data, part two + func populate(restaurant: Restaurant) { + // Displaying data, part two - nameLabel.text = restaurant.name - cityLabel.text = restaurant.city - categoryLabel.text = restaurant.category - starsView.rating = Int(restaurant.averageRating.rounded()) - priceLabel.text = priceString(from: restaurant.price) + nameLabel.text = restaurant.name + cityLabel.text = restaurant.city + categoryLabel.text = restaurant.category + starsView.rating = Int(restaurant.averageRating.rounded()) + priceLabel.text = priceString(from: restaurant.price) - let image = restaurant.photo - thumbnailView.sd_setImage(with: image) - } + let image = restaurant.photo + thumbnailView.sd_setImage(with: image) + } - override func prepareForReuse() { - super.prepareForReuse() - thumbnailView.sd_cancelCurrentImageLoad() - } + override func prepareForReuse() { + super.prepareForReuse() + thumbnailView.sd_cancelCurrentImageLoad() + } } diff --git a/functions/FunctionsExample/FunctionsExample.xcodeproj/project.pbxproj b/functions/FunctionsExample/FunctionsExample.xcodeproj/project.pbxproj index 7647eb9a6..82a0f5611 100644 --- a/functions/FunctionsExample/FunctionsExample.xcodeproj/project.pbxproj +++ b/functions/FunctionsExample/FunctionsExample.xcodeproj/project.pbxproj @@ -720,7 +720,7 @@ repositoryURL = "https://github.com/firebase/firebase-ios-sdk"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 11.0.0; + minimumVersion = 13.1.1; }; }; /* End XCRemoteSwiftPackageReference section */ diff --git a/inappmessaging/InAppMessagingExample.xcodeproj/project.pbxproj b/inappmessaging/InAppMessagingExample.xcodeproj/project.pbxproj index 2ff292ae3..1865b3927 100644 --- a/inappmessaging/InAppMessagingExample.xcodeproj/project.pbxproj +++ b/inappmessaging/InAppMessagingExample.xcodeproj/project.pbxproj @@ -7,7 +7,7 @@ objects = { /* Begin PBXBuildFile section */ - 1CAA5060E4A9D8B55A15EB4E /* GoogleService-Info.plist in Sources */ = {isa = PBXBuildFile; fileRef = 81A2FB070295F56CC0B2D523 /* GoogleService-Info.plist */; }; + 1CAA5060E4A9D8B55A15EB4E /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 81A2FB070295F56CC0B2D523 /* GoogleService-Info.plist */; }; 8DB25F8F20BE0C29000ABEE2 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8DB25F8E20BE0C29000ABEE2 /* AppDelegate.swift */; }; 8DB25F9120BE0C29000ABEE2 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8DB25F9020BE0C29000ABEE2 /* ViewController.swift */; }; 8DB25F9420BE0C29000ABEE2 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8DB25F9220BE0C29000ABEE2 /* Main.storyboard */; }; @@ -145,6 +145,8 @@ 8DB25F9920BE0C2A000ABEE2 /* LaunchScreen.storyboard in Resources */, 8DB25F9620BE0C2A000ABEE2 /* Assets.xcassets in Resources */, 8DB25F9420BE0C29000ABEE2 /* Main.storyboard in Resources */, + + 1CAA5060E4A9D8B55A15EB4E /* GoogleService-Info.plist in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -157,7 +159,6 @@ files = ( 8DB25F9120BE0C29000ABEE2 /* ViewController.swift in Sources */, 8DB25F8F20BE0C29000ABEE2 /* AppDelegate.swift in Sources */, - 1CAA5060E4A9D8B55A15EB4E /* GoogleService-Info.plist in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -364,7 +365,7 @@ repositoryURL = "https://github.com/firebase/firebase-ios-sdk"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 11.7.0; + minimumVersion = 13.1.1; }; }; /* End XCRemoteSwiftPackageReference section */ diff --git a/installations/InstallationsExample.xcodeproj/project.pbxproj b/installations/InstallationsExample.xcodeproj/project.pbxproj index 92763d0f2..8b9291e6f 100644 --- a/installations/InstallationsExample.xcodeproj/project.pbxproj +++ b/installations/InstallationsExample.xcodeproj/project.pbxproj @@ -7,7 +7,6 @@ objects = { /* Begin PBXBuildFile section */ - 1D96FE8F5661BCC40ADE48D3 /* GoogleService-Info.plist in Sources */ = {isa = PBXBuildFile; fileRef = AF38C66CDB0155B6F2BDE0FD /* GoogleService-Info.plist */; }; 8DC73D842D41ABC00092B6EE /* FirebaseInstallations in Frameworks */ = {isa = PBXBuildFile; productRef = 8DC73D832D41ABC00092B6EE /* FirebaseInstallations */; }; 9A0A6EE922CE55B2004CF0FF /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A0A6EE822CE55B2004CF0FF /* AppDelegate.swift */; }; 9A0A6EEB22CE55B2004CF0FF /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A0A6EEA22CE55B2004CF0FF /* ViewController.swift */; }; @@ -15,7 +14,7 @@ 9A5908BF23AD479B000CD3B9 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9A5908BA23AD479B000CD3B9 /* LaunchScreen.xib */; }; 9A5908C323AD479B000CD3B9 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9A5908BC23AD479B000CD3B9 /* Images.xcassets */; }; 9A76AE3122E20A0C0024E473 /* InstallationsExampleTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9A76AE3022E20A0C0024E473 /* InstallationsExampleTests.m */; }; - B2B6F60F66458C91106FE457 /* GoogleService-Info.plist in Sources */ = {isa = PBXBuildFile; fileRef = AF38C66CDB0155B6F2BDE0FD /* GoogleService-Info.plist */; }; + B2B6F60F66458C91106FE457 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = AF38C66CDB0155B6F2BDE0FD /* GoogleService-Info.plist */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -208,6 +207,8 @@ 9A5908C323AD479B000CD3B9 /* Images.xcassets in Resources */, 9A5908BF23AD479B000CD3B9 /* LaunchScreen.xib in Resources */, 9A0A6EEE22CE55B2004CF0FF /* Main.storyboard in Resources */, + + B2B6F60F66458C91106FE457 /* GoogleService-Info.plist in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -227,7 +228,6 @@ files = ( 9A0A6EEB22CE55B2004CF0FF /* ViewController.swift in Sources */, 9A0A6EE922CE55B2004CF0FF /* AppDelegate.swift in Sources */, - B2B6F60F66458C91106FE457 /* GoogleService-Info.plist in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -236,7 +236,6 @@ buildActionMask = 2147483647; files = ( 9A76AE3122E20A0C0024E473 /* InstallationsExampleTests.m in Sources */, - 1D96FE8F5661BCC40ADE48D3 /* GoogleService-Info.plist in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -490,7 +489,7 @@ repositoryURL = "https://github.com/firebase/firebase-ios-sdk"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 11.7.0; + minimumVersion = 13.1.1; }; }; /* End XCRemoteSwiftPackageReference section */ diff --git a/messaging/MessagingExample.xcodeproj/project.pbxproj b/messaging/MessagingExample.xcodeproj/project.pbxproj index bba95ee9e..2a2ffa71d 100644 --- a/messaging/MessagingExample.xcodeproj/project.pbxproj +++ b/messaging/MessagingExample.xcodeproj/project.pbxproj @@ -10,13 +10,12 @@ 0718C8BD2E8F256800AA7788 /* NotificationServiceExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 0718C8B62E8F256800AA7788 /* NotificationServiceExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 0738A4882E8F2B2A00680EC4 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0738A4862E8F2B2A00680EC4 /* NotificationService.swift */; }; 107347AB20315A3A004A66D1 /* MessagingExampleUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 107347AA20315A3A004A66D1 /* MessagingExampleUITests.swift */; }; - 5B334EAC56E0F5E167C81718 /* GoogleService-Info.plist in Sources */ = {isa = PBXBuildFile; fileRef = 82E79B6D15A982EAE7B0E31B /* GoogleService-Info.plist */; }; + 5B334EAC56E0F5E167C81718 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 82E79B6D15A982EAE7B0E31B /* GoogleService-Info.plist */; }; 5F5A537E1ADE67D500F81DF0 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F5A537D1ADE67D500F81DF0 /* AppDelegate.swift */; }; 5F5A53801ADE67D500F81DF0 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F5A537F1ADE67D500F81DF0 /* ViewController.swift */; }; 5F99610B1AE0CF4F0034F503 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5F9961061AE0CF4F0034F503 /* Images.xcassets */; }; 5F99610D1AE0CF4F0034F503 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5F9961071AE0CF4F0034F503 /* LaunchScreen.xib */; }; 8DC73D8C2D41AE850092B6EE /* FirebaseMessaging in Frameworks */ = {isa = PBXBuildFile; productRef = 8DC73D8B2D41AE850092B6EE /* FirebaseMessaging */; }; - 978E6F2CAAC2AD673D158985 /* GoogleService-Info.plist in Sources */ = {isa = PBXBuildFile; fileRef = 82E79B6D15A982EAE7B0E31B /* GoogleService-Info.plist */; }; EA2B4E6C2ED4F571003F9A40 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = EA2B4E662ED4F571003F9A40 /* Main.storyboard */; }; EA2B4E752ED4F571003F9A40 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = EA2B4E662ED4F571003F9A40 /* Main.storyboard */; }; EAC902A32EC6849A000761B8 /* FirebaseMessaging in Frameworks */ = {isa = PBXBuildFile; productRef = EAC902A22EC6849A000761B8 /* FirebaseMessaging */; }; @@ -325,6 +324,8 @@ 5F99610D1AE0CF4F0034F503 /* LaunchScreen.xib in Resources */, 5F99610B1AE0CF4F0034F503 /* Images.xcassets in Resources */, EA2B4E752ED4F571003F9A40 /* Main.storyboard in Resources */, + + 5B334EAC56E0F5E167C81718 /* GoogleService-Info.plist in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -344,7 +345,6 @@ buildActionMask = 2147483647; files = ( 107347AB20315A3A004A66D1 /* MessagingExampleUITests.swift in Sources */, - 978E6F2CAAC2AD673D158985 /* GoogleService-Info.plist in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -354,7 +354,6 @@ files = ( 5F5A53801ADE67D500F81DF0 /* ViewController.swift in Sources */, 5F5A537E1ADE67D500F81DF0 /* AppDelegate.swift in Sources */, - 5B334EAC56E0F5E167C81718 /* GoogleService-Info.plist in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -733,7 +732,7 @@ repositoryURL = "https://github.com/firebase/firebase-ios-sdk"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 11.7.0; + minimumVersion = 13.1.1; }; }; /* End XCRemoteSwiftPackageReference section */ diff --git a/performance/PerformanceExample.xcodeproj/project.pbxproj b/performance/PerformanceExample.xcodeproj/project.pbxproj index 730196c4f..0d5899018 100644 --- a/performance/PerformanceExample.xcodeproj/project.pbxproj +++ b/performance/PerformanceExample.xcodeproj/project.pbxproj @@ -754,7 +754,7 @@ repositoryURL = "https://github.com/firebase/firebase-ios-sdk.git"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 12.0.0; + minimumVersion = 13.1.1; }; }; /* End XCRemoteSwiftPackageReference section */ diff --git a/scripts/test.sh b/scripts/test.sh index 620510bf6..910c7d75e 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -126,6 +126,14 @@ if [[ "$OS" == catalyst ]];then ) fi + +if [[ "$xcode_major" -ge 27 ]]; then + flags+=( + MACOSX_DEPLOYMENT_TARGET=12.0 + WATCHOS_DEPLOYMENT_TARGET=9.0 + ) +fi + flags+=( CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO diff --git a/storage/StorageExample.xcodeproj/project.pbxproj b/storage/StorageExample.xcodeproj/project.pbxproj index c49d181ec..fd07ad591 100644 --- a/storage/StorageExample.xcodeproj/project.pbxproj +++ b/storage/StorageExample.xcodeproj/project.pbxproj @@ -833,7 +833,7 @@ repositoryURL = "https://github.com/firebase/firebase-ios-sdk"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 11.0.0; + minimumVersion = 13.1.1; }; }; /* End XCRemoteSwiftPackageReference section */ diff --git a/swiftui/PasswordlessSwiftUI/PasswordlessSwiftUI.xcodeproj/project.pbxproj b/swiftui/PasswordlessSwiftUI/PasswordlessSwiftUI.xcodeproj/project.pbxproj index f070c8f51..306db5a04 100644 --- a/swiftui/PasswordlessSwiftUI/PasswordlessSwiftUI.xcodeproj/project.pbxproj +++ b/swiftui/PasswordlessSwiftUI/PasswordlessSwiftUI.xcodeproj/project.pbxproj @@ -348,7 +348,7 @@ repositoryURL = "https://github.com/firebase/firebase-ios-sdk"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 12.6.0; + minimumVersion = 13.1.1; }; }; /* End XCRemoteSwiftPackageReference section */ diff --git a/swiftui/PasswordlessSwiftUI/PasswordlessSwiftUI/GoogleService-Info.plist b/swiftui/PasswordlessSwiftUI/PasswordlessSwiftUI/GoogleService-Info.plist new file mode 100644 index 000000000..af55cfef7 --- /dev/null +++ b/swiftui/PasswordlessSwiftUI/PasswordlessSwiftUI/GoogleService-Info.plist @@ -0,0 +1,40 @@ + + + + + AD_UNIT_ID_FOR_BANNER_TEST + ca-app-pub-3940256099942544/2934735716 + AD_UNIT_ID_FOR_INTERSTITIAL_TEST + ca-app-pub-3940256099942544/4411468910 + API_KEY + AIzaSyAzlj4APqi5S58nFtE52Da0fYBOHA2MhaY + BUNDLE_ID + id + CLIENT_ID + 123456789000-hjugbg6ud799v4c49dim8ce2usclthar.apps.googleusercontent.com + DATABASE_URL + https://mockproject-1234.firebaseio.com + GCM_SENDER_ID + 123456789000 + GOOGLE_APP_ID + 1:123456789000:ios:f1bf012572b04063 + IS_ADS_ENABLED + + IS_ANALYTICS_ENABLED + + IS_APPINVITE_ENABLED + + IS_GCM_ENABLED + + IS_SIGNIN_ENABLED + + PLIST_VERSION + 1 + PROJECT_ID + mockproject-1234 + REVERSED_CLIENT_ID + com.googleusercontent.apps.123456789000-hjugbg6ud799v4c49dim8ce2usclthar + STORAGE_BUCKET + mockproject-1234.appspot.com + + From c45456cc47d21d9693741c516f5bacea395afb23 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Tue, 27 Oct 2026 18:35:20 -0400 Subject: [PATCH 2/6] style: format Swift files in authentication and firestore samples Apply swiftformat rules via scripts/style.sh to resolve CI style check failures. --- .../AuthenticationExampleUITests.swift | 408 ++++++------ .../RestaurantsTableViewController.swift | 602 +++++++++--------- 2 files changed, 511 insertions(+), 499 deletions(-) diff --git a/authentication/AuthenticationExampleUITests/AuthenticationExampleUITests.swift b/authentication/AuthenticationExampleUITests/AuthenticationExampleUITests.swift index 7c3d3a8b1..484cc8b28 100644 --- a/authentication/AuthenticationExampleUITests/AuthenticationExampleUITests.swift +++ b/authentication/AuthenticationExampleUITests/AuthenticationExampleUITests.swift @@ -16,244 +16,244 @@ import XCTest @MainActor class AuthenticationExampleUITests: XCTestCase { - var app: XCUIApplication! + var app: XCUIApplication! - override func setUp() { - super.setUp() + override func setUp() { + super.setUp() - continueAfterFailure = false + continueAfterFailure = false - app = XCUIApplication() - app.launch() - } - - override func tearDown() { - super.tearDown() - signOut() - } - - func testAuth() { - // Verify that Auth Example app launched successfully - XCTAssertTrue(app.navigationBars["Firebase Auth"].exists) - } - - func testAuthOptions() { - // There are 12 sign in methods, each with its own cell - XCTAssertEqual(app.tables.cells.count, 12) - } - - func testAuthAnonymously() { - app.staticTexts["Anonymous Authentication"].tap() - - wait(forElement: app.navigationBars["User"], timeout: 5.0) - XCTAssertTrue(app.navigationBars["User"].exists) - - let isAnonymousCell = app.cells.containing(.staticText, identifier: "Is User Anonymous?") - .element - XCTAssertTrue(isAnonymousCell.staticTexts["Yes"].exists, "The user should be anonymous") - } - - func testAuthExistingAccount() { - app.staticTexts["Email & Password Login"].tap() - - let testEmail = "test@test.com" - app.textFields["Email"].tap() - app.textFields["Email"].typeText(testEmail) - - let testPassword = "test12" - app.textFields["Password"].tap() - app.textFields["Password"].typeText(testPassword) - - app.buttons["Login"].tap() - - wait(forElement: app.navigationBars["User"], timeout: 5.0) - XCTAssertTrue(app.navigationBars["User"].exists) - XCTAssertTrue( - app.staticTexts[testEmail].exists, - "The user should be signed in and the email field should display their email." - ) - } - - func testAuthExistingAccountWrongPassword() { - app.staticTexts["Email & Password Login"].tap() - - let testEmail = "test@test.com" - app.textFields["Email"].tap() - app.textFields["Email"].typeText(testEmail) + app = XCUIApplication() + app.launch() + } - app.textFields["Password"].tap() - app.textFields["Password"].typeText("wrong password") + override func tearDown() { + super.tearDown() + signOut() + } + + func testAuth() { + // Verify that Auth Example app launched successfully + XCTAssertTrue(app.navigationBars["Firebase Auth"].exists) + } - app.buttons["Login"].tap() + func testAuthOptions() { + // There are 12 sign in methods, each with its own cell + XCTAssertEqual(app.tables.cells.count, 12) + } - wait(forElement: app.alerts.staticTexts["Error"], timeout: 5.0) - XCTAssertTrue(app.alerts.staticTexts["Error"].exists) + func testAuthAnonymously() { + app.staticTexts["Anonymous Authentication"].tap() - // Dismiss alert that password was incorrect - app.alerts.buttons["OK"].tap() + wait(forElement: app.navigationBars["User"], timeout: 5.0) + XCTAssertTrue(app.navigationBars["User"].exists) - // Go back and check that there is no user that is signed in - app.navigationBars.buttons.firstMatch.tap() - app.tabBars.firstMatch.buttons.element(boundBy: 1).tap() - wait(forElement: app.navigationBars["User"], timeout: 5.0) - XCTAssertEqual( - app.cells.count, - 0, - "The user shouldn't be signed in and the user view should have no cells." - ) - } - - func testCreateAccountBadPassword() { - app.staticTexts["Email & Password Login"].tap() - - let testEmail = "test@test.com" - app.textFields["Email"].tap() - app.textFields["Email"].typeText(testEmail) + let isAnonymousCell = app.cells.containing(.staticText, identifier: "Is User Anonymous?") + .element + XCTAssertTrue(isAnonymousCell.staticTexts["Yes"].exists, "The user should be anonymous") + } - app.textFields["Password"].tap() - // Enter an invalid password that is "too short" - app.textFields["Password"].typeText("2shrt") + func testAuthExistingAccount() { + app.staticTexts["Email & Password Login"].tap() - app.buttons["Create Account"].tap() + let testEmail = "test@test.com" + app.textFields["Email"].tap() + app.textFields["Email"].typeText(testEmail) + + let testPassword = "test12" + app.textFields["Password"].tap() + app.textFields["Password"].typeText(testPassword) + + app.buttons["Login"].tap() - wait(forElement: app.alerts.staticTexts["Error"], timeout: 5.0) - XCTAssertTrue(app.alerts.staticTexts["Error"].exists) + wait(forElement: app.navigationBars["User"], timeout: 5.0) + XCTAssertTrue(app.navigationBars["User"].exists) + XCTAssertTrue( + app.staticTexts[testEmail].exists, + "The user should be signed in and the email field should display their email." + ) + } - // Dismiss alert that password was incorrect - app.alerts.buttons["OK"].tap() + func testAuthExistingAccountWrongPassword() { + app.staticTexts["Email & Password Login"].tap() - // Go back and check that there is no user that is signed in - app.navigationBars.buttons.firstMatch.tap() - app.tabBars.firstMatch.buttons.element(boundBy: 1).tap() - wait(forElement: app.navigationBars["User"], timeout: 5.0) - XCTAssertEqual( - app.cells.count, - 0, - "The user shouldn't be signed in and the user view should have no cells." - ) + let testEmail = "test@test.com" + app.textFields["Email"].tap() + app.textFields["Email"].typeText(testEmail) + + app.textFields["Password"].tap() + app.textFields["Password"].typeText("wrong password") + + app.buttons["Login"].tap() + + wait(forElement: app.alerts.staticTexts["Error"], timeout: 5.0) + XCTAssertTrue(app.alerts.staticTexts["Error"].exists) + + // Dismiss alert that password was incorrect + app.alerts.buttons["OK"].tap() + + // Go back and check that there is no user that is signed in + app.navigationBars.buttons.firstMatch.tap() + app.tabBars.firstMatch.buttons.element(boundBy: 1).tap() + wait(forElement: app.navigationBars["User"], timeout: 5.0) + XCTAssertEqual( + app.cells.count, + 0, + "The user shouldn't be signed in and the user view should have no cells." + ) + } + + func testCreateAccountBadPassword() { + app.staticTexts["Email & Password Login"].tap() + + let testEmail = "test@test.com" + app.textFields["Email"].tap() + app.textFields["Email"].typeText(testEmail) + + app.textFields["Password"].tap() + // Enter an invalid password that is "too short" + app.textFields["Password"].typeText("2shrt") + + app.buttons["Create Account"].tap() + + wait(forElement: app.alerts.staticTexts["Error"], timeout: 5.0) + XCTAssertTrue(app.alerts.staticTexts["Error"].exists) + + // Dismiss alert that password was incorrect + app.alerts.buttons["OK"].tap() + + // Go back and check that there is no user that is signed in + app.navigationBars.buttons.firstMatch.tap() + app.tabBars.firstMatch.buttons.element(boundBy: 1).tap() + wait(forElement: app.navigationBars["User"], timeout: 5.0) + XCTAssertEqual( + app.cells.count, + 0, + "The user shouldn't be signed in and the user view should have no cells." + ) + } + + func testCreateAlreadyExistingAccount() { + app.staticTexts["Email & Password Login"].tap() + + let testEmail = "test@test.com" + app.textFields["Email"].tap() + app.textFields["Email"].typeText(testEmail) + + let testPassword = "test12" + app.textFields["Password"].tap() + app.textFields["Password"].typeText(testPassword) + + app.buttons["Create Account"].tap() + + wait(forElement: app.alerts.staticTexts["Error"], timeout: 5.0) + XCTAssertTrue(app.alerts.staticTexts["Error"].exists) + + // Dismiss alert that password was incorrect + app.alerts.buttons["OK"].tap() + + // Go back and check that there is no user that is signed in + app.navigationBars.buttons.firstMatch.tap() + app.tabBars.firstMatch.buttons.element(boundBy: 1).tap() + wait(forElement: app.navigationBars["User"], timeout: 5.0) + XCTAssertEqual( + app.cells.count, + 0, + "The user shouldn't be signed in and the user view should have no cells." + ) + } + + func testCreateAccountCorrectPassword() { + app.staticTexts["Email & Password Login"].tap() + + let newEmail = "\(Date().timeIntervalSince1970)_test@test.com" + app.textFields["Email"].tap() + app.typeText(newEmail) + + let newPassword = "new password" + app.textFields["Password"].tap() + app.typeText(newPassword) + + app.buttons["Create Account"].tap() + + wait(forElement: app.navigationBars["User"], timeout: 5.0) + XCTAssertTrue(app.navigationBars["User"].exists) + XCTAssertTrue( + app.staticTexts[newEmail].exists, + "The user should be signed into the new account." + ) + } + + func DRAFT_testGoogleSignInAndLinkAccount() { + let interruptionMonitor = addUIInterruptionMonitor(withDescription: "Sign in with Google") { + (alert) -> Bool in + alert.buttons["Continue"].tap() + return true } - func testCreateAlreadyExistingAccount() { - app.staticTexts["Email & Password Login"].tap() - - let testEmail = "test@test.com" - app.textFields["Email"].tap() - app.textFields["Email"].typeText(testEmail) - - let testPassword = "test12" - app.textFields["Password"].tap() - app.textFields["Password"].typeText(testPassword) + app.staticTexts["Google"].tap() - app.buttons["Create Account"].tap() + app.tap() // Triggers the UIInterruptionMonitor - wait(forElement: app.alerts.staticTexts["Error"], timeout: 5.0) - XCTAssertTrue(app.alerts.staticTexts["Error"].exists) + let testEmail = "" + let testPassword = "" - // Dismiss alert that password was incorrect - app.alerts.buttons["OK"].tap() + let firstTimeLogin = app.webViews.containing(.textField, identifier: "Email or phone") + .element.exists + if firstTimeLogin { + app.webViews.textFields.firstMatch.tap() - // Go back and check that there is no user that is signed in - app.navigationBars.buttons.firstMatch.tap() - app.tabBars.firstMatch.buttons.element(boundBy: 1).tap() - wait(forElement: app.navigationBars["User"], timeout: 5.0) - XCTAssertEqual( - app.cells.count, - 0, - "The user shouldn't be signed in and the user view should have no cells." - ) - } + app.webViews.textFields.firstMatch.typeText(testEmail) - func testCreateAccountCorrectPassword() { - app.staticTexts["Email & Password Login"].tap() + app.buttons["Done"].tap() // Dismiss keyboard + app.buttons["Next"].tap() // Transition to Google sign in password page - let newEmail = "\(Date().timeIntervalSince1970)_test@test.com" - app.textFields["Email"].tap() - app.typeText(newEmail) + app.webViews.secureTextFields.firstMatch.tap() - let newPassword = "new password" - app.textFields["Password"].tap() - app.typeText(newPassword) + app.webViews.secureTextFields.firstMatch.typeText(testPassword) - app.buttons["Create Account"].tap() + app.buttons["Done"].tap() // Dismiss keyboard + app.buttons["Next"].tap() // Complete sign in - wait(forElement: app.navigationBars["User"], timeout: 5.0) - XCTAssertTrue(app.navigationBars["User"].exists) - XCTAssertTrue( - app.staticTexts[newEmail].exists, - "The user should be signed into the new account." - ) + } else { + app.webViews.staticTexts[testEmail].tap() } - func DRAFT_testGoogleSignInAndLinkAccount() { - let interruptionMonitor = addUIInterruptionMonitor(withDescription: "Sign in with Google") { - (alert) -> Bool in - alert.buttons["Continue"].tap() - return true - } - - app.staticTexts["Google"].tap() - - app.tap() // Triggers the UIInterruptionMonitor - - let testEmail = "" - let testPassword = "" - - let firstTimeLogin = app.webViews.containing(.textField, identifier: "Email or phone") - .element.exists - if firstTimeLogin { - app.webViews.textFields.firstMatch.tap() + wait(forElement: app.navigationBars["User"], timeout: 5.0) - app.webViews.textFields.firstMatch.typeText(testEmail) + XCTAssertTrue(app.navigationBars["User"].exists) + XCTAssertTrue(app.staticTexts[testEmail].exists) - app.buttons["Done"].tap() // Dismiss keyboard - app.buttons["Next"].tap() // Transition to Google sign in password page + // Cleanup + removeUIInterruptionMonitor(interruptionMonitor) + } - app.webViews.secureTextFields.firstMatch.tap() - - app.webViews.secureTextFields.firstMatch.typeText(testPassword) - - app.buttons["Done"].tap() // Dismiss keyboard - app.buttons["Next"].tap() // Complete sign in - - } else { - app.webViews.staticTexts[testEmail].tap() - } - - wait(forElement: app.navigationBars["User"], timeout: 5.0) - - XCTAssertTrue(app.navigationBars["User"].exists) - XCTAssertTrue(app.staticTexts[testEmail].exists) - - // Cleanup - removeUIInterruptionMonitor(interruptionMonitor) - } - - func testLaunchPerformance() { - if #available(macOS 10.15, iOS 13.0, tvOS 13.0, *) { - // This measures how long it takes to launch your application. - measure(metrics: [XCTOSSignpostMetric.applicationLaunch]) { - XCUIApplication().launch() - } - } + func testLaunchPerformance() { + if #available(macOS 10.15, iOS 13.0, tvOS 13.0, *) { + // This measures how long it takes to launch your application. + measure(metrics: [XCTOSSignpostMetric.applicationLaunch]) { + XCUIApplication().launch() + } } + } - // MARK: - Private Helpers + // MARK: - Private Helpers - private func signOut() { - app.tabBars.firstMatch.buttons.element(boundBy: 1).tap() - wait(forElement: app.navigationBars["User"], timeout: 5.0) - if app.staticTexts["Sign Out"].exists { - app.staticTexts["Sign Out"].tap() - } - app.tabBars.firstMatch.buttons.element(boundBy: 0).tap() + private func signOut() { + app.tabBars.firstMatch.buttons.element(boundBy: 1).tap() + wait(forElement: app.navigationBars["User"], timeout: 5.0) + if app.staticTexts["Sign Out"].exists { + app.staticTexts["Sign Out"].tap() } + app.tabBars.firstMatch.buttons.element(boundBy: 0).tap() + } } extension XCTestCase { - @MainActor - func wait(forElement element: XCUIElement, timeout: TimeInterval) { - let predicate = NSPredicate(format: "exists == 1") - expectation(for: predicate, evaluatedWith: element) - waitForExpectations(timeout: timeout) - } + @MainActor + func wait(forElement element: XCUIElement, timeout: TimeInterval) { + let predicate = NSPredicate(format: "exists == 1") + expectation(for: predicate, evaluatedWith: element) + waitForExpectations(timeout: timeout) + } } diff --git a/firestore/FirestoreExample/RestaurantsTableViewController.swift b/firestore/FirestoreExample/RestaurantsTableViewController.swift index da8d23674..0546e6d09 100644 --- a/firestore/FirestoreExample/RestaurantsTableViewController.swift +++ b/firestore/FirestoreExample/RestaurantsTableViewController.swift @@ -19,346 +19,358 @@ import FirebaseFirestore import UIKit func priceString(from price: Int) -> String { - let priceText: String - switch price { - case 1: - priceText = "$" - case 2: - priceText = "$$" - case 3: - priceText = "$$$" - case _: - fatalError("price must be between one and three") - } - - return priceText + let priceText: String + switch price { + case 1: + priceText = "$" + case 2: + priceText = "$$" + case 3: + priceText = "$$$" + case _: + fatalError("price must be between one and three") + } + + return priceText } class RestaurantsTableViewController: UIViewController, UITableViewDataSource, UITableViewDelegate { - @IBOutlet var tableView: UITableView! - @IBOutlet var activeFiltersStackView: UIStackView! - @IBOutlet var stackViewHeightConstraint: NSLayoutConstraint! + @IBOutlet var tableView: UITableView! + @IBOutlet var activeFiltersStackView: UIStackView! + @IBOutlet var stackViewHeightConstraint: NSLayoutConstraint! - @IBOutlet var cityFilterLabel: UILabel! - @IBOutlet var categoryFilterLabel: UILabel! - @IBOutlet var priceFilterLabel: UILabel! + @IBOutlet var cityFilterLabel: UILabel! + @IBOutlet var categoryFilterLabel: UILabel! + @IBOutlet var priceFilterLabel: UILabel! - let backgroundView = UIImageView() + let backgroundView = UIImageView() - private var restaurants: [Restaurant] = [] - private var documents: [DocumentSnapshot] = [] + private var restaurants: [Restaurant] = [] + private var documents: [DocumentSnapshot] = [] - fileprivate var query: Query? { - didSet { - if let listener = listener { - listener.remove() - observeQuery() - } - } + fileprivate var query: Query? { + didSet { + if let listener = listener { + listener.remove() + observeQuery() + } } - - private var listener: ListenerRegistration? - - fileprivate func observeQuery() { - guard let query = query else { return } - stopObserving() - - // Display data from Firestore, part one - - listener = query.addSnapshotListener { [unowned self] snapshot, error in - guard let snapshot = snapshot else { - print("Error fetching snapshot results: \(error!)") - return - } - let models = snapshot.documents.map { (document) -> Restaurant in - let maybeModel: Restaurant? - do { - maybeModel = try document.data(as: Restaurant.self) - } catch { - fatalError( - "Unable to initialize type \(Restaurant.self) with dictionary \(document.data()): \(error)" - ) - } - - if let model = maybeModel { - return model - } else { - // Don't use fatalError here in a real app. - fatalError("Missing document of type \(Restaurant.self) at \(document.reference.path)") - } - } - self.restaurants = models - self.documents = snapshot.documents - - if self.documents.count > 0 { - self.tableView.backgroundView = nil - } else { - self.tableView.backgroundView = self.backgroundView - } - - self.tableView.reloadData() + } + + private var listener: ListenerRegistration? + + fileprivate func observeQuery() { + guard let query = query else { return } + stopObserving() + + // Display data from Firestore, part one + + listener = query.addSnapshotListener { [unowned self] snapshot, error in + guard let snapshot = snapshot else { + print("Error fetching snapshot results: \(error!)") + return + } + let models = snapshot.documents.map { (document) -> Restaurant in + let maybeModel: Restaurant? + do { + maybeModel = try document.data(as: Restaurant.self) + } catch { + fatalError( + "Unable to initialize type \(Restaurant.self) with dictionary \(document.data()): \(error)" + ) } - } - fileprivate func stopObserving() { - listener?.remove() - } + if let model = maybeModel { + return model + } else { + // Don't use fatalError here in a real app. + fatalError( + "Missing document of type \(Restaurant.self) at \(document.reference.path)" + ) + } + } + self.restaurants = models + self.documents = snapshot.documents - fileprivate func baseQuery() -> Query { - return Firestore.firestore().collection("restaurants").limit(to: 50) - } + if self.documents.count > 0 { + self.tableView.backgroundView = nil + } else { + self.tableView.backgroundView = self.backgroundView + } - private lazy var filters: (navigationController: UINavigationController, - filtersController: FiltersViewController) = { - FiltersViewController.fromStoryboard(delegate: self) - }() - - override func viewDidLoad() { - super.viewDidLoad() - backgroundView.image = UIImage(named: "pizza-monster")! - backgroundView.contentMode = .scaleAspectFit - backgroundView.alpha = 0.5 - tableView.backgroundView = backgroundView - tableView.tableFooterView = UIView() - - navigationController?.navigationBar.applyFirebaseAppearance() - - tableView.dataSource = self - tableView.delegate = self - query = baseQuery() - stackViewHeightConstraint.constant = 0 - activeFiltersStackView.isHidden = true + self.tableView.reloadData() } - - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - setNeedsStatusBarAppearanceUpdate() - observeQuery() + } + + fileprivate func stopObserving() { + listener?.remove() + } + + fileprivate func baseQuery() -> Query { + return Firestore.firestore().collection("restaurants").limit(to: 50) + } + + private lazy var filters: (navigationController: UINavigationController, + filtersController: FiltersViewController) = { + FiltersViewController.fromStoryboard(delegate: self) + }() + + override func viewDidLoad() { + super.viewDidLoad() + backgroundView.image = UIImage(named: "pizza-monster")! + backgroundView.contentMode = .scaleAspectFit + backgroundView.alpha = 0.5 + tableView.backgroundView = backgroundView + tableView.tableFooterView = UIView() + + navigationController?.navigationBar.applyFirebaseAppearance() + + tableView.dataSource = self + tableView.delegate = self + query = baseQuery() + stackViewHeightConstraint.constant = 0 + activeFiltersStackView.isHidden = true + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + setNeedsStatusBarAppearanceUpdate() + observeQuery() + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + let underlyingAuth = Auth.auth() + if underlyingAuth.currentUser == nil { + underlyingAuth.signInAnonymously() } - - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - let underlyingAuth = Auth.auth() - if underlyingAuth.currentUser == nil { - underlyingAuth.signInAnonymously() + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + stopObserving() + } + + @IBAction func didTapPopulateButton(_: Any) { + let words = [ + "Bar", + "Fire", + "Grill", + "Drive Thru", + "Place", + "Best", + "Spot", + "Prime", + "Eatin'", + ] + + let cities = Restaurant.cities + let categories = Restaurant.categories + + for _ in 0 ..< 20 { + let randomIndexes = (Int(arc4random_uniform(UInt32(words.count))), + Int(arc4random_uniform(UInt32(words.count)))) + let name = words[randomIndexes.0] + " " + words[randomIndexes.1] + let category = categories[Int(arc4random_uniform(UInt32(categories.count)))] + let city = cities[Int(arc4random_uniform(UInt32(cities.count)))] + let price = Int(arc4random_uniform(3)) + 1 + let photo = Restaurant.imageURL(forName: name) + + // Basic writes + + let collection = Firestore.firestore().collection("restaurants") + + let restaurant = Restaurant( + name: name, + category: category, + city: city, + price: price, + ratingCount: 10, + averageRating: 0, + photo: photo + ) + + let restaurantRef = collection.document() + do { + try restaurantRef.setData(from: restaurant) + } catch { + fatalError("Encoding Restaurant failed: \(error)") + } + + let batch = Firestore.firestore().batch() + guard let user = Auth.auth().currentUser else { continue } + var average: Float = 0 + for _ in 0 ..< 10 { + let rating = Int(arc4random_uniform(5) + 1) + average += Float(rating) / 10 + let text = rating > 3 ? "good" : "food was too spicy" + let review = Review(rating: rating, + userID: user.uid, + username: user.displayName ?? "Anonymous", + text: text, + date: Timestamp()) + let ratingRef = restaurantRef.collection("ratings").document() + do { + try batch.setData(from: review, forDocument: ratingRef) + } catch { + fatalError("Encoding Rating failed: \(error)") } + } + batch.updateData(["avgRating": average], forDocument: restaurantRef) + batch.commit(completion: { error in + guard let error = error else { return } + print("Error generating reviews: \(error). Check your Firestore permissions.") + }) } - - override func viewWillDisappear(_ animated: Bool) { - super.viewWillDisappear(animated) - stopObserving() + } + + @IBAction func didTapClearButton(_: Any) { + filters.filtersController.clearFilters() + controller( + filters.filtersController, + didSelectCategory: nil, + city: nil, + price: nil, + sortBy: nil + ) + } + + @IBAction func didTapFilterButton(_: Any) { + present(filters.navigationController, animated: true, completion: nil) + } + + override var preferredStatusBarStyle: UIStatusBarStyle { + set {} + get { + return .lightContent } + } + + deinit { + listener?.remove() + } + + // MARK: - UITableViewDataSource + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "RestaurantTableViewCell", + for: indexPath) as! RestaurantTableViewCell + let restaurant = restaurants[indexPath.row] + cell.populate(restaurant: restaurant) + return cell + } + + func tableView(_: UITableView, numberOfRowsInSection _: Int) -> Int { + return restaurants.count + } + + // MARK: - UITableViewDelegate + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) + let controller = RestaurantDetailViewController.fromStoryboard() + controller.titleImageURL = restaurants[indexPath.row].photo + controller.restaurant = restaurants[indexPath.row] + controller.restaurantReference = documents[indexPath.row].reference + navigationController?.pushViewController(controller, animated: true) + } +} - @IBAction func didTapPopulateButton(_: Any) { - let words = ["Bar", "Fire", "Grill", "Drive Thru", "Place", "Best", "Spot", "Prime", "Eatin'"] - - let cities = Restaurant.cities - let categories = Restaurant.categories - - for _ in 0 ..< 20 { - let randomIndexes = (Int(arc4random_uniform(UInt32(words.count))), - Int(arc4random_uniform(UInt32(words.count)))) - let name = words[randomIndexes.0] + " " + words[randomIndexes.1] - let category = categories[Int(arc4random_uniform(UInt32(categories.count)))] - let city = cities[Int(arc4random_uniform(UInt32(cities.count)))] - let price = Int(arc4random_uniform(3)) + 1 - let photo = Restaurant.imageURL(forName: name) - - // Basic writes - - let collection = Firestore.firestore().collection("restaurants") - - let restaurant = Restaurant( - name: name, - category: category, - city: city, - price: price, - ratingCount: 10, - averageRating: 0, - photo: photo - ) - - let restaurantRef = collection.document() - do { - try restaurantRef.setData(from: restaurant) - } catch { - fatalError("Encoding Restaurant failed: \(error)") - } - - let batch = Firestore.firestore().batch() - guard let user = Auth.auth().currentUser else { continue } - var average: Float = 0 - for _ in 0 ..< 10 { - let rating = Int(arc4random_uniform(5) + 1) - average += Float(rating) / 10 - let text = rating > 3 ? "good" : "food was too spicy" - let review = Review(rating: rating, - userID: user.uid, - username: user.displayName ?? "Anonymous", - text: text, - date: Timestamp()) - let ratingRef = restaurantRef.collection("ratings").document() - do { - try batch.setData(from: review, forDocument: ratingRef) - } catch { - fatalError("Encoding Rating failed: \(error)") - } - } - batch.updateData(["avgRating": average], forDocument: restaurantRef) - batch.commit(completion: { error in - guard let error = error else { return } - print("Error generating reviews: \(error). Check your Firestore permissions.") - }) - } +extension RestaurantsTableViewController: FiltersViewControllerDelegate { + func query(withCategory category: String?, city: String?, price: Int?, + sortBy: String?) -> Query { + var filtered = baseQuery() + + if category == nil, city == nil, price == nil, sortBy == nil { + stackViewHeightConstraint.constant = 0 + activeFiltersStackView.isHidden = true + } else { + stackViewHeightConstraint.constant = 44 + activeFiltersStackView.isHidden = false } - @IBAction func didTapClearButton(_: Any) { - filters.filtersController.clearFilters() - controller( - filters.filtersController, - didSelectCategory: nil, - city: nil, - price: nil, - sortBy: nil - ) - } + // Advanced queries - @IBAction func didTapFilterButton(_: Any) { - present(filters.navigationController, animated: true, completion: nil) + if let category = category, !category.isEmpty { + filtered = filtered.whereField("category", isEqualTo: category) } - override var preferredStatusBarStyle: UIStatusBarStyle { - set {} - get { - return .lightContent - } + if let city = city, !city.isEmpty { + filtered = filtered.whereField("city", isEqualTo: city) } - deinit { - listener?.remove() + if let price = price { + filtered = filtered.whereField("price", isEqualTo: price) } - // MARK: - UITableViewDataSource - - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: "RestaurantTableViewCell", - for: indexPath) as! RestaurantTableViewCell - let restaurant = restaurants[indexPath.row] - cell.populate(restaurant: restaurant) - return cell + if let sortBy = sortBy, !sortBy.isEmpty { + filtered = filtered.order(by: sortBy) } - func tableView(_: UITableView, numberOfRowsInSection _: Int) -> Int { - return restaurants.count + return filtered + } + + func controller(_: FiltersViewController, + didSelectCategory category: String?, + city: String?, + price: Int?, + sortBy: String?) { + let filtered = query(withCategory: category, city: city, price: price, sortBy: sortBy) + + if let category = category, !category.isEmpty { + categoryFilterLabel.text = category + categoryFilterLabel.isHidden = false + } else { + categoryFilterLabel.isHidden = true } - // MARK: - UITableViewDelegate - - func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - tableView.deselectRow(at: indexPath, animated: true) - let controller = RestaurantDetailViewController.fromStoryboard() - controller.titleImageURL = restaurants[indexPath.row].photo - controller.restaurant = restaurants[indexPath.row] - controller.restaurantReference = documents[indexPath.row].reference - navigationController?.pushViewController(controller, animated: true) + if let city = city, !city.isEmpty { + cityFilterLabel.text = city + cityFilterLabel.isHidden = false + } else { + cityFilterLabel.isHidden = true } -} -extension RestaurantsTableViewController: FiltersViewControllerDelegate { - func query(withCategory category: String?, city: String?, price: Int?, sortBy: String?) -> Query { - var filtered = baseQuery() - - if category == nil, city == nil, price == nil, sortBy == nil { - stackViewHeightConstraint.constant = 0 - activeFiltersStackView.isHidden = true - } else { - stackViewHeightConstraint.constant = 44 - activeFiltersStackView.isHidden = false - } - - // Advanced queries - - if let category = category, !category.isEmpty { - filtered = filtered.whereField("category", isEqualTo: category) - } - - if let city = city, !city.isEmpty { - filtered = filtered.whereField("city", isEqualTo: city) - } - - if let price = price { - filtered = filtered.whereField("price", isEqualTo: price) - } - - if let sortBy = sortBy, !sortBy.isEmpty { - filtered = filtered.order(by: sortBy) - } - - return filtered + if let price = price { + priceFilterLabel.text = priceString(from: price) + priceFilterLabel.isHidden = false + } else { + priceFilterLabel.isHidden = true } - func controller(_: FiltersViewController, - didSelectCategory category: String?, - city: String?, - price: Int?, - sortBy: String?) - { - let filtered = query(withCategory: category, city: city, price: price, sortBy: sortBy) - - if let category = category, !category.isEmpty { - categoryFilterLabel.text = category - categoryFilterLabel.isHidden = false - } else { - categoryFilterLabel.isHidden = true - } - - if let city = city, !city.isEmpty { - cityFilterLabel.text = city - cityFilterLabel.isHidden = false - } else { - cityFilterLabel.isHidden = true - } - - if let price = price { - priceFilterLabel.text = priceString(from: price) - priceFilterLabel.isHidden = false - } else { - priceFilterLabel.isHidden = true - } - - query = filtered - observeQuery() - } + query = filtered + observeQuery() + } } class RestaurantTableViewCell: UITableViewCell { - @IBOutlet private var thumbnailView: UIImageView! + @IBOutlet private var thumbnailView: UIImageView! - @IBOutlet private var nameLabel: UILabel! + @IBOutlet private var nameLabel: UILabel! - @IBOutlet var starsView: ImmutableStarsView! + @IBOutlet var starsView: ImmutableStarsView! - @IBOutlet private var cityLabel: UILabel! + @IBOutlet private var cityLabel: UILabel! - @IBOutlet private var categoryLabel: UILabel! + @IBOutlet private var categoryLabel: UILabel! - @IBOutlet private var priceLabel: UILabel! + @IBOutlet private var priceLabel: UILabel! - func populate(restaurant: Restaurant) { - // Displaying data, part two + func populate(restaurant: Restaurant) { + // Displaying data, part two - nameLabel.text = restaurant.name - cityLabel.text = restaurant.city - categoryLabel.text = restaurant.category - starsView.rating = Int(restaurant.averageRating.rounded()) - priceLabel.text = priceString(from: restaurant.price) + nameLabel.text = restaurant.name + cityLabel.text = restaurant.city + categoryLabel.text = restaurant.category + starsView.rating = Int(restaurant.averageRating.rounded()) + priceLabel.text = priceString(from: restaurant.price) - let image = restaurant.photo - thumbnailView.sd_setImage(with: image) - } + let image = restaurant.photo + thumbnailView.sd_setImage(with: image) + } - override func prepareForReuse() { - super.prepareForReuse() - thumbnailView.sd_cancelCurrentImageLoad() - } + override func prepareForReuse() { + super.prepareForReuse() + thumbnailView.sd_cancelCurrentImageLoad() + } } From 57a34bdcfc9fc2ee25cbf5b8b591a667463e5c5d Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Tue, 27 Oct 2026 18:35:20 -0400 Subject: [PATCH 3/6] build(abtesting): bump watchOS deployment target to 8.0 FirebaseRemoteConfig in firebase-ios-sdk 13.1.1 requires a minimum deployment target of watchOS 8.0, whereas ABTestingExample was still configured for watchOS 7.0. --- abtesting/ABTestingExample.xcodeproj/project.pbxproj | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/abtesting/ABTestingExample.xcodeproj/project.pbxproj b/abtesting/ABTestingExample.xcodeproj/project.pbxproj index a093dc367..53fbebf71 100644 --- a/abtesting/ABTestingExample.xcodeproj/project.pbxproj +++ b/abtesting/ABTestingExample.xcodeproj/project.pbxproj @@ -835,7 +835,7 @@ SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; TVOS_DEPLOYMENT_TARGET = 15.0; - WATCHOS_DEPLOYMENT_TARGET = 7.0; + WATCHOS_DEPLOYMENT_TARGET = 8.0; }; name = Debug; }; @@ -896,7 +896,7 @@ SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_OPTIMIZATION_LEVEL = "-O"; TVOS_DEPLOYMENT_TARGET = 15.0; - WATCHOS_DEPLOYMENT_TARGET = 7.0; + WATCHOS_DEPLOYMENT_TARGET = 8.0; }; name = Release; }; @@ -1310,7 +1310,7 @@ SKIP_INSTALL = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 4; - WATCHOS_DEPLOYMENT_TARGET = 7.0; + WATCHOS_DEPLOYMENT_TARGET = 8.0; }; name = Debug; }; @@ -1333,7 +1333,7 @@ SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 4; VALIDATE_PRODUCT = YES; - WATCHOS_DEPLOYMENT_TARGET = 7.0; + WATCHOS_DEPLOYMENT_TARGET = 8.0; }; name = Release; }; @@ -1350,7 +1350,7 @@ SKIP_INSTALL = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 4; - WATCHOS_DEPLOYMENT_TARGET = 7.0; + WATCHOS_DEPLOYMENT_TARGET = 8.0; }; name = Debug; }; @@ -1368,7 +1368,7 @@ SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 4; VALIDATE_PRODUCT = YES; - WATCHOS_DEPLOYMENT_TARGET = 7.0; + WATCHOS_DEPLOYMENT_TARGET = 8.0; }; name = Release; }; From 39bc1480ebbaf2e3218783a0a5a056fb12804291 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Tue, 27 Oct 2026 18:35:20 -0400 Subject: [PATCH 4/6] fix(ci): remove redundant plist project injection in installations prereq InstallationsExample.xcodeproj already includes GoogleService-Info.plist in its Resources build phase. Running info_script.rb in CI added a second reference and caused an unexpected duplicate tasks build error. --- scripts/install_prereqs/installations.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/install_prereqs/installations.sh b/scripts/install_prereqs/installations.sh index 82a6c47d9..5adb367f4 100755 --- a/scripts/install_prereqs/installations.sh +++ b/scripts/install_prereqs/installations.sh @@ -1,3 +1,5 @@ +#!/usr/bin/env bash + # Copyright 2019 Google # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,5 +15,4 @@ # limitations under the License. cp ../mock-GoogleService-Info.plist ./GoogleService-Info.plist -ruby ../scripts/info_script.rb Installations sed -i '' '/BUNDLE_ID Date: Tue, 27 Oct 2026 18:35:20 -0400 Subject: [PATCH 5/6] fix(ci): run firestore workflow on macos-26 with Xcode 26.4 Xcode 26.2 does not enable default SwiftPM package traits for remote package references in Xcode projects, causing FirebaseFirestore in firebase-ios-sdk 13.1.1 to fail with FIRESTORE_TRAIT_ENABLED undefined. Run the Firestore workflow on macos-26 with Xcode 26.4 where package traits are supported. --- .github/workflows/firestore.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/firestore.yml b/.github/workflows/firestore.yml index bfcfb3a3c..e7039575b 100644 --- a/.github/workflows/firestore.yml +++ b/.github/workflows/firestore.yml @@ -31,19 +31,19 @@ env: jobs: spm: - runs-on: macOS-15 + runs-on: macos-26 env: SPM: true OS: iOS DIR: firestore - DEVICE: iPhone 16 + DEVICE: iPhone 17 Pro TEST: false SCHEME: FirestoreExample steps: - name: Checkout uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: Xcode - run: sudo xcode-select -s /Applications/Xcode_26.2.app/Contents/Developer + run: sudo xcode-select -s /Applications/Xcode_26.4.app/Contents/Developer - name: Setup run: | cd firestore From 82fda37bbd28f2589a324da5fdd17227c141b9ae Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Tue, 27 Oct 2026 18:48:17 -0400 Subject: [PATCH 6/6] fix(ci): warm up simulator service and retry transient xcodebuild failures Warm up CoreSimulatorService via xcrun simctl before invoking xcodebuild after xcode-select switches, and retry once if xcodebuild fails due to transient simulator discovery issues. --- scripts/test.sh | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/scripts/test.sh b/scripts/test.sh index 910c7d75e..9da46ae9e 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -48,6 +48,8 @@ if [[ -z "${TEST:-}" ]]; then fi # Set have_secrets to true or false. +have_secrets=false +# shellcheck disable=SC1091 source scripts/check_secrets.sh # Initialize flags @@ -153,7 +155,12 @@ fi function xcb() { echo xcodebuild "$@" - xcodebuild "$@" | xcpretty + xcrun simctl list devices > /dev/null 2>&1 || true + if ! xcodebuild "$@" | xcpretty; then + echo "xcodebuild failed; retrying once..." + xcrun simctl list devices > /dev/null 2>&1 || true + xcodebuild "$@" | xcpretty + fi } # Run xcodebuild