diff --git a/FirebaseUI.xcodeproj/project.pbxproj b/FirebaseUI.xcodeproj/project.pbxproj index 13af95729f8..3192166802f 100644 --- a/FirebaseUI.xcodeproj/project.pbxproj +++ b/FirebaseUI.xcodeproj/project.pbxproj @@ -3,11 +3,10 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 47; objects = { /* Begin PBXBuildFile section */ - 062B15392FA061BBBEC6CDC4 /* libPods-Facebook.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FE33CCA3B13AA53771BEC0B4 /* libPods-Facebook.a */; }; 1073FE661CE509EC00E2F73F /* facebook.png in Resources */ = {isa = PBXBuildFile; fileRef = 1073FE1B1CE509EC00E2F73F /* facebook.png */; }; 1073FE671CE509EC00E2F73F /* facebook@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 1073FE1C1CE509EC00E2F73F /* facebook@2x.png */; }; 1073FE681CE509EC00E2F73F /* facebook@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 1073FE1D1CE509EC00E2F73F /* facebook@3x.png */; }; @@ -79,8 +78,13 @@ 1073FEB61CE50A8F00E2F73F /* FirebaseCollectionViewDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 1073FEAD1CE50A8F00E2F73F /* FirebaseCollectionViewDataSource.m */; }; 1073FEB71CE50A8F00E2F73F /* FirebaseDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 1073FEAE1CE50A8F00E2F73F /* FirebaseDataSource.m */; }; 1073FEB81CE50A8F00E2F73F /* FirebaseTableViewDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 1073FEAF1CE50A8F00E2F73F /* FirebaseTableViewDataSource.m */; }; - 79F1B3CFC3F6E157C28C30F5 /* libPods-Auth.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 2F28F3BD9C664DEB963595FF /* libPods-Auth.a */; }; - 8D7710365523A745DAA1321E /* libPods-FirebaseUI.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 275B4831F13A9ADD39B47692 /* libPods-FirebaseUI.a */; }; + 8D2CB5E21D53F5AE0097FEEB /* libFirebaseUI.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D8C579A61B57349000899F86 /* libFirebaseUI.a */; }; + 8D2CB5E91D53F5C50097FEEB /* FirebaseArrayTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8D2CB5E81D53F5C50097FEEB /* FirebaseArrayTest.m */; }; + 8DD3AACA1D5940E000E6B1F9 /* FirebaseCollectionViewDataSourceTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8DD3AAC91D5940E000E6B1F9 /* FirebaseCollectionViewDataSourceTest.m */; }; + 8DD3AACD1D5942C900E6B1F9 /* FirebaseArrayTestUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 8DD3AACC1D5942C900E6B1F9 /* FirebaseArrayTestUtils.m */; }; + 8DD498F41D5BAAA30022D37E /* FirebaseTableViewDataSourceTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8DD498F31D5BAAA30022D37E /* FirebaseTableViewDataSourceTest.m */; }; + 8DFCD1371D53FE8D00208D7B /* FIRAuthUISignInButton.h in Headers */ = {isa = PBXBuildFile; fileRef = 8DFCD1351D53FE8D00208D7B /* FIRAuthUISignInButton.h */; }; + 8DFCD1381D53FE8D00208D7B /* FIRAuthUISignInButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 8DFCD1361D53FE8D00208D7B /* FIRAuthUISignInButton.m */; }; D8666CB71CEAFCD5002B7C26 /* FirebaseArray.m in Sources */ = {isa = PBXBuildFile; fileRef = 1073FEAC1CE50A8F00E2F73F /* FirebaseArray.m */; }; D8666CB81CEAFCD5002B7C26 /* FirebaseCollectionViewDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 1073FEAD1CE50A8F00E2F73F /* FirebaseCollectionViewDataSource.m */; }; D8666CB91CEAFCD5002B7C26 /* FirebaseDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 1073FEAE1CE50A8F00E2F73F /* FirebaseDataSource.m */; }; @@ -171,10 +175,16 @@ D8666D601CEBFB5C002B7C26 /* FirebaseDatabaseUI.h in Headers */ = {isa = PBXBuildFile; fileRef = D8666D5E1CEBFB5C002B7C26 /* FirebaseDatabaseUI.h */; }; D8B6ACE51B58383C005CDDB2 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D8C579BB1B5837DF00899F86 /* UIKit.framework */; }; D8B6ACE71B583877005CDDB2 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D8B6ACE61B583877005CDDB2 /* Foundation.framework */; }; - F6761ADEA0F0FE70AC2B8134 /* libPods-Google.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 40CC80AD6E673BED46049F08 /* libPods-Google.a */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + 8D2CB5E31D53F5AE0097FEEB /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D8C5799E1B57349000899F86 /* Project object */; + proxyType = 1; + remoteGlobalIDString = D8C579A51B57349000899F86; + remoteInfo = FirebaseUI; + }; D8666D541CEB036C002B7C26 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = D8C5799E1B57349000899F86 /* Project object */; @@ -241,7 +251,6 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 0A1917FE3607B78B018B843D /* Pods-Auth.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Auth.release.xcconfig"; path = "Pods/Target Support Files/Pods-Auth/Pods-Auth.release.xcconfig"; sourceTree = ""; }; 1073FE1B1CE509EC00E2F73F /* facebook.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = facebook.png; sourceTree = ""; }; 1073FE1C1CE509EC00E2F73F /* facebook@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "facebook@2x.png"; sourceTree = ""; }; 1073FE1D1CE509EC00E2F73F /* facebook@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "facebook@3x.png"; sourceTree = ""; }; @@ -313,18 +322,16 @@ 1073FEAD1CE50A8F00E2F73F /* FirebaseCollectionViewDataSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = FirebaseCollectionViewDataSource.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 1073FEAE1CE50A8F00E2F73F /* FirebaseDataSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FirebaseDataSource.m; sourceTree = ""; }; 1073FEAF1CE50A8F00E2F73F /* FirebaseTableViewDataSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = FirebaseTableViewDataSource.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; - 275B4831F13A9ADD39B47692 /* libPods-FirebaseUI.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-FirebaseUI.a"; sourceTree = BUILT_PRODUCTS_DIR; }; - 2F28F3BD9C664DEB963595FF /* libPods-Auth.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Auth.a"; sourceTree = BUILT_PRODUCTS_DIR; }; - 38A0F57C03EF230E5A720BD1 /* Pods-FirebaseUI.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FirebaseUI.release.xcconfig"; path = "Pods/Target Support Files/Pods-FirebaseUI/Pods-FirebaseUI.release.xcconfig"; sourceTree = ""; }; - 40CC80AD6E673BED46049F08 /* libPods-Google.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Google.a"; sourceTree = BUILT_PRODUCTS_DIR; }; - 51FE96CCEB38B9896FA8763F /* Pods-Facebook.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Facebook.release.xcconfig"; path = "Pods/Target Support Files/Pods-Facebook/Pods-Facebook.release.xcconfig"; sourceTree = ""; }; - 6A47DCC5F13C82AD936429CD /* Pods-Facebook.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Facebook.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Facebook/Pods-Facebook.debug.xcconfig"; sourceTree = ""; }; - 9DC5493B51C2AA187A0CADC3 /* Pods-Auth.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Auth.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Auth/Pods-Auth.debug.xcconfig"; sourceTree = ""; }; - A19CDD03BA9195E469EC5BAB /* Pods-Google.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Google.release.xcconfig"; path = "Pods/Target Support Files/Pods-Google/Pods-Google.release.xcconfig"; sourceTree = ""; }; - A82808FA5C9B636BDCD554A1 /* Pods-Database.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Database.release.xcconfig"; path = "Pods/Target Support Files/Pods-Database/Pods-Database.release.xcconfig"; sourceTree = ""; }; - B06216F85B72D25E7A394865 /* Pods-Database.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Database.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Database/Pods-Database.debug.xcconfig"; sourceTree = ""; }; - D6A4679357D8650D724CF886 /* Pods-Google.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Google.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Google/Pods-Google.debug.xcconfig"; sourceTree = ""; }; - D8199F2217B396C2D50E9224 /* Pods-FirebaseUI.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FirebaseUI.debug.xcconfig"; path = "Pods/Target Support Files/Pods-FirebaseUI/Pods-FirebaseUI.debug.xcconfig"; sourceTree = ""; }; + 8D2CB5DD1D53F5AE0097FEEB /* FirebaseUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FirebaseUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 8D2CB5E11D53F5AE0097FEEB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 8D2CB5E81D53F5C50097FEEB /* FirebaseArrayTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FirebaseArrayTest.m; sourceTree = ""; }; + 8D3AA95C1D53F9C7006FC9AF /* libPods-FirebaseUI.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = "libPods-FirebaseUI.a"; path = "../../Library/Developer/Xcode/DerivedData/FirebaseUI-fwcyvciocjlrhqbyjmmybozelsll/Build/Products/Debug-iphonesimulator/libPods-FirebaseUI.a"; sourceTree = ""; }; + 8DD3AAC91D5940E000E6B1F9 /* FirebaseCollectionViewDataSourceTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FirebaseCollectionViewDataSourceTest.m; sourceTree = ""; }; + 8DD3AACB1D5942C900E6B1F9 /* FirebaseArrayTestUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FirebaseArrayTestUtils.h; sourceTree = ""; }; + 8DD3AACC1D5942C900E6B1F9 /* FirebaseArrayTestUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FirebaseArrayTestUtils.m; sourceTree = ""; }; + 8DD498F31D5BAAA30022D37E /* FirebaseTableViewDataSourceTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FirebaseTableViewDataSourceTest.m; sourceTree = ""; }; + 8DFCD1351D53FE8D00208D7B /* FIRAuthUISignInButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FIRAuthUISignInButton.h; sourceTree = ""; }; + 8DFCD1361D53FE8D00208D7B /* FIRAuthUISignInButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIRAuthUISignInButton.m; sourceTree = ""; }; D8666CAE1CEAFC93002B7C26 /* libDatabase.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libDatabase.a; sourceTree = BUILT_PRODUCTS_DIR; }; D8666CC61CEAFD40002B7C26 /* libAuth.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libAuth.a; sourceTree = BUILT_PRODUCTS_DIR; }; D8666CFE1CEAFDDB002B7C26 /* libPods-FirebaseUI.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = "libPods-FirebaseUI.a"; path = "../../../Library/Developer/Xcode/DerivedData/FirebaseUI-anhduclyafjbjahglzespojfewme/Build/Products/Debug-iphonesimulator/libPods-FirebaseUI.a"; sourceTree = ""; }; @@ -352,11 +359,17 @@ D8B6ACE61B583877005CDDB2 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; D8C579A61B57349000899F86 /* libFirebaseUI.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libFirebaseUI.a; sourceTree = BUILT_PRODUCTS_DIR; }; D8C579BB1B5837DF00899F86 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; - E7EBD66D7AB946A019E62B7F /* libPods-Database.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Database.a"; sourceTree = BUILT_PRODUCTS_DIR; }; - FE33CCA3B13AA53771BEC0B4 /* libPods-Facebook.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Facebook.a"; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 8D2CB5DA1D53F5AE0097FEEB /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 8D2CB5E21D53F5AE0097FEEB /* libFirebaseUI.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; D8666CAB1CEAFC93002B7C26 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -382,7 +395,6 @@ D8666D391CEB0210002B7C26 /* Security.framework in Frameworks */, D8666D371CEB01F0002B7C26 /* FirebaseAnalytics.framework in Frameworks */, D8666D351CEB01E2002B7C26 /* FirebaseAuth.framework in Frameworks */, - 79F1B3CFC3F6E157C28C30F5 /* libPods-Auth.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -390,7 +402,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 062B15392FA061BBBEC6CDC4 /* libPods-Facebook.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -399,7 +410,6 @@ buildActionMask = 2147483647; files = ( D8666D591CEB03BA002B7C26 /* GoogleSignIn.framework in Frameworks */, - F6761ADEA0F0FE70AC2B8134 /* libPods-Google.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -409,30 +419,12 @@ files = ( D8B6ACE51B58383C005CDDB2 /* UIKit.framework in Frameworks */, D8B6ACE71B583877005CDDB2 /* Foundation.framework in Frameworks */, - 8D7710365523A745DAA1321E /* libPods-FirebaseUI.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 0A468E95D1E218FE7DFFCF11 /* Pods */ = { - isa = PBXGroup; - children = ( - D8199F2217B396C2D50E9224 /* Pods-FirebaseUI.debug.xcconfig */, - 38A0F57C03EF230E5A720BD1 /* Pods-FirebaseUI.release.xcconfig */, - 9DC5493B51C2AA187A0CADC3 /* Pods-Auth.debug.xcconfig */, - 0A1917FE3607B78B018B843D /* Pods-Auth.release.xcconfig */, - B06216F85B72D25E7A394865 /* Pods-Database.debug.xcconfig */, - A82808FA5C9B636BDCD554A1 /* Pods-Database.release.xcconfig */, - 6A47DCC5F13C82AD936429CD /* Pods-Facebook.debug.xcconfig */, - 51FE96CCEB38B9896FA8763F /* Pods-Facebook.release.xcconfig */, - D6A4679357D8650D724CF886 /* Pods-Google.debug.xcconfig */, - A19CDD03BA9195E469EC5BAB /* Pods-Google.release.xcconfig */, - ); - name = Pods; - sourceTree = ""; - }; 1073FE171CE509EC00E2F73F /* Auth */ = { isa = PBXGroup; children = ( @@ -569,6 +561,8 @@ 1073FE471CE509EC00E2F73F /* FIRAuthUIErrors.m */, 1073FE481CE509EC00E2F73F /* FIRAuthUIErrorUtils.h */, 1073FE491CE509EC00E2F73F /* FIRAuthUIErrorUtils.m */, + 8DFCD1351D53FE8D00208D7B /* FIRAuthUISignInButton.h */, + 8DFCD1361D53FE8D00208D7B /* FIRAuthUISignInButton.m */, 1073FE4A1CE509EC00E2F73F /* FIRAuthUIStrings.h */, 1073FE4B1CE509EC00E2F73F /* FIRAuthUIStrings.m */, 1073FE4C1CE509EC00E2F73F /* FIRAuthUITableHeaderView.h */, @@ -639,9 +633,31 @@ path = Implementation; sourceTree = ""; }; + 8D2CB5DE1D53F5AE0097FEEB /* FirebaseUITests */ = { + isa = PBXGroup; + children = ( + 8DD3AACE1D5942D000E6B1F9 /* Helpers */, + 8D2CB5E81D53F5C50097FEEB /* FirebaseArrayTest.m */, + 8DD3AAC91D5940E000E6B1F9 /* FirebaseCollectionViewDataSourceTest.m */, + 8DD498F31D5BAAA30022D37E /* FirebaseTableViewDataSourceTest.m */, + 8D2CB5E11D53F5AE0097FEEB /* Info.plist */, + ); + path = FirebaseUITests; + sourceTree = ""; + }; + 8DD3AACE1D5942D000E6B1F9 /* Helpers */ = { + isa = PBXGroup; + children = ( + 8DD3AACB1D5942C900E6B1F9 /* FirebaseArrayTestUtils.h */, + 8DD3AACC1D5942C900E6B1F9 /* FirebaseArrayTestUtils.m */, + ); + name = Helpers; + sourceTree = ""; + }; D8B6ACE81B5839A5005CDDB2 /* Frameworks */ = { isa = PBXGroup; children = ( + 8D3AA95C1D53F9C7006FC9AF /* libPods-FirebaseUI.a */, D8666D581CEB03BA002B7C26 /* GoogleSignIn.framework */, D8666D521CEB02A9002B7C26 /* SystemConfiguration.framework */, D8666D501CEB02A4002B7C26 /* StoreKit.framework */, @@ -663,11 +679,6 @@ D8666CFE1CEAFDDB002B7C26 /* libPods-FirebaseUI.a */, D8B6ACE61B583877005CDDB2 /* Foundation.framework */, D8C579BB1B5837DF00899F86 /* UIKit.framework */, - 275B4831F13A9ADD39B47692 /* libPods-FirebaseUI.a */, - 2F28F3BD9C664DEB963595FF /* libPods-Auth.a */, - E7EBD66D7AB946A019E62B7F /* libPods-Database.a */, - FE33CCA3B13AA53771BEC0B4 /* libPods-Facebook.a */, - 40CC80AD6E673BED46049F08 /* libPods-Google.a */, ); name = Frameworks; sourceTree = ""; @@ -675,10 +686,10 @@ D8C5799D1B57349000899F86 = { isa = PBXGroup; children = ( - D8B6ACE81B5839A5005CDDB2 /* Frameworks */, D8C579A81B57349000899F86 /* FirebaseUI */, + 8D2CB5DE1D53F5AE0097FEEB /* FirebaseUITests */, D8C579A71B57349000899F86 /* Products */, - 0A468E95D1E218FE7DFFCF11 /* Pods */, + D8B6ACE81B5839A5005CDDB2 /* Frameworks */, ); sourceTree = ""; }; @@ -690,6 +701,7 @@ D8666CC61CEAFD40002B7C26 /* libAuth.a */, D8666D071CEAFF29002B7C26 /* libFacebook.a */, D8666D141CEAFF41002B7C26 /* libGoogle.a */, + 8D2CB5DD1D53F5AE0097FEEB /* FirebaseUITests.xctest */, ); name = Products; sourceTree = ""; @@ -779,6 +791,7 @@ 1073FE971CE509EC00E2F73F /* FIRPasswordRecoveryViewController.h in Headers */, 1073FE741CE509EC00E2F73F /* FIRGoogleAuthUI.h in Headers */, 1073FE8E1CE509EC00E2F73F /* FIRAuthUITableViewCell.h in Headers */, + 8DFCD1371D53FE8D00208D7B /* FIRAuthUISignInButton.h in Headers */, 1073FE7D1CE509EC00E2F73F /* FIRAuthPickerViewController.h in Headers */, 1073FEB21CE50A8F00E2F73F /* FirebaseCollectionViewDataSource.h in Headers */, 1073FE881CE509EC00E2F73F /* FIRAuthUIErrorUtils.h in Headers */, @@ -798,16 +811,37 @@ /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ + 8D2CB5DC1D53F5AE0097FEEB /* FirebaseUITests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 8D2CB5E71D53F5AE0097FEEB /* Build configuration list for PBXNativeTarget "FirebaseUITests" */; + buildPhases = ( + 4A531CC030045B93042803FA /* [CP] Check Pods Manifest.lock */, + 8D2CB5D91D53F5AE0097FEEB /* Sources */, + 8D2CB5DA1D53F5AE0097FEEB /* Frameworks */, + 8D2CB5DB1D53F5AE0097FEEB /* Resources */, + B4BB8A5983D129F23E140373 /* [CP] Embed Pods Frameworks */, + CC1F8BDF974CFA17969663CA /* [CP] Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + 8D2CB5E41D53F5AE0097FEEB /* PBXTargetDependency */, + ); + name = FirebaseUITests; + productName = FirebaseUITests; + productReference = 8D2CB5DD1D53F5AE0097FEEB /* FirebaseUITests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; D8666CAD1CEAFC93002B7C26 /* Database */ = { isa = PBXNativeTarget; buildConfigurationList = D8666CB61CEAFC93002B7C26 /* Build configuration list for PBXNativeTarget "Database" */; buildPhases = ( - EE4FB8B0A2310732DCF60111 /* 📦 Check Pods Manifest.lock */, + EE4FB8B0A2310732DCF60111 /* [CP] Check Pods Manifest.lock */, D8666CAA1CEAFC93002B7C26 /* Sources */, D8666CAB1CEAFC93002B7C26 /* Frameworks */, D8666CAC1CEAFC93002B7C26 /* CopyFiles */, D8666CBB1CEAFCFB002B7C26 /* Headers */, - EDF383BBC4F3A6CDD217F06A /* 📦 Copy Pods Resources */, + EDF383BBC4F3A6CDD217F06A /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -822,13 +856,13 @@ isa = PBXNativeTarget; buildConfigurationList = D8666CCC1CEAFD40002B7C26 /* Build configuration list for PBXNativeTarget "Auth" */; buildPhases = ( - 4F409FB222EDC7574E52A97F /* 📦 Check Pods Manifest.lock */, + 4F409FB222EDC7574E52A97F /* [CP] Check Pods Manifest.lock */, D8666CC21CEAFD40002B7C26 /* Sources */, D8666CC31CEAFD40002B7C26 /* Frameworks */, D8666CC41CEAFD40002B7C26 /* CopyFiles */, D8666CDD1CEAFD73002B7C26 /* Headers */, D8666CEF1CEAFD9E002B7C26 /* Resources */, - 0F661CA715BBA079DC42B8E0 /* 📦 Copy Pods Resources */, + 0F661CA715BBA079DC42B8E0 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -843,11 +877,11 @@ isa = PBXNativeTarget; buildConfigurationList = D8666D0F1CEAFF29002B7C26 /* Build configuration list for PBXNativeTarget "Facebook" */; buildPhases = ( - D17D52CDF1B7F0B80C57DD59 /* 📦 Check Pods Manifest.lock */, + D17D52CDF1B7F0B80C57DD59 /* [CP] Check Pods Manifest.lock */, D8666D031CEAFF29002B7C26 /* Sources */, D8666D041CEAFF29002B7C26 /* Frameworks */, D8666D051CEAFF29002B7C26 /* CopyFiles */, - 61350DBC677183EF5C0F3C30 /* 📦 Copy Pods Resources */, + 61350DBC677183EF5C0F3C30 /* [CP] Copy Pods Resources */, D8666D1E1CEAFF9D002B7C26 /* Headers */, D8666D211CEAFFA7002B7C26 /* Resources */, ); @@ -865,11 +899,11 @@ isa = PBXNativeTarget; buildConfigurationList = D8666D1A1CEAFF41002B7C26 /* Build configuration list for PBXNativeTarget "Google" */; buildPhases = ( - 072ABD579ED6CE7C8FC98658 /* 📦 Check Pods Manifest.lock */, + 072ABD579ED6CE7C8FC98658 /* [CP] Check Pods Manifest.lock */, D8666D101CEAFF41002B7C26 /* Sources */, D8666D111CEAFF41002B7C26 /* Frameworks */, D8666D121CEAFF41002B7C26 /* CopyFiles */, - 7DC3FAEEEC64FFC78D855373 /* 📦 Copy Pods Resources */, + 7DC3FAEEEC64FFC78D855373 /* [CP] Copy Pods Resources */, D8666D271CEAFFC2002B7C26 /* Headers */, D8666D2A1CEAFFD9002B7C26 /* Resources */, ); @@ -887,14 +921,13 @@ isa = PBXNativeTarget; buildConfigurationList = D8C579AF1B57349000899F86 /* Build configuration list for PBXNativeTarget "FirebaseUI" */; buildPhases = ( - D1854EF2AF0C2648CD5C535D /* 📦 Check Pods Manifest.lock */, + D1854EF2AF0C2648CD5C535D /* [CP] Check Pods Manifest.lock */, D8C579A21B57349000899F86 /* Sources */, D8C579A31B57349000899F86 /* Frameworks */, D8C579A41B57349000899F86 /* Copy Files */, D8B6ACF71B583D31005CDDB2 /* Headers */, - D8B6ADA21B58DB8B005CDDB2 /* Build Framework */, D809A1321BF7F30C000257AA /* Resources */, - 1ECE9CCEBE1B8C2962053639 /* 📦 Copy Pods Resources */, + 1ECE9CCEBE1B8C2962053639 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -915,6 +948,9 @@ LastUpgradeCheck = 0700; ORGANIZATIONNAME = "Firebase, Inc."; TargetAttributes = { + 8D2CB5DC1D53F5AE0097FEEB = { + CreatedOnToolsVersion = 7.3.1; + }; D8666CAD1CEAFC93002B7C26 = { CreatedOnToolsVersion = 7.3.1; }; @@ -933,7 +969,7 @@ }; }; buildConfigurationList = D8C579A11B57349000899F86 /* Build configuration list for PBXProject "FirebaseUI" */; - compatibilityVersion = "Xcode 3.2"; + compatibilityVersion = "Xcode 6.3"; developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( @@ -949,11 +985,19 @@ D8666CC51CEAFD40002B7C26 /* Auth */, D8666D061CEAFF29002B7C26 /* Facebook */, D8666D131CEAFF41002B7C26 /* Google */, + 8D2CB5DC1D53F5AE0097FEEB /* FirebaseUITests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 8D2CB5DB1D53F5AE0097FEEB /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; D809A1321BF7F30C000257AA /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -1035,14 +1079,14 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 072ABD579ED6CE7C8FC98658 /* 📦 Check Pods Manifest.lock */ = { + 072ABD579ED6CE7C8FC98658 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "📦 Check Pods Manifest.lock"; + name = "[CP] Check Pods Manifest.lock"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; @@ -1050,14 +1094,14 @@ shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; showEnvVarsInLog = 0; }; - 0F661CA715BBA079DC42B8E0 /* 📦 Copy Pods Resources */ = { + 0F661CA715BBA079DC42B8E0 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "📦 Copy Pods Resources"; + name = "[CP] Copy Pods Resources"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; @@ -1065,14 +1109,14 @@ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Auth/Pods-Auth-resources.sh\"\n"; showEnvVarsInLog = 0; }; - 1ECE9CCEBE1B8C2962053639 /* 📦 Copy Pods Resources */ = { + 1ECE9CCEBE1B8C2962053639 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "📦 Copy Pods Resources"; + name = "[CP] Copy Pods Resources"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; @@ -1080,14 +1124,29 @@ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-FirebaseUI/Pods-FirebaseUI-resources.sh\"\n"; showEnvVarsInLog = 0; }; - 4F409FB222EDC7574E52A97F /* 📦 Check Pods Manifest.lock */ = { + 4A531CC030045B93042803FA /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Check Pods Manifest.lock"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; + showEnvVarsInLog = 0; + }; + 4F409FB222EDC7574E52A97F /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "📦 Check Pods Manifest.lock"; + name = "[CP] Check Pods Manifest.lock"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; @@ -1095,14 +1154,14 @@ shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; showEnvVarsInLog = 0; }; - 61350DBC677183EF5C0F3C30 /* 📦 Copy Pods Resources */ = { + 61350DBC677183EF5C0F3C30 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "📦 Copy Pods Resources"; + name = "[CP] Copy Pods Resources"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; @@ -1110,14 +1169,14 @@ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Facebook/Pods-Facebook-resources.sh\"\n"; showEnvVarsInLog = 0; }; - 7DC3FAEEEC64FFC78D855373 /* 📦 Copy Pods Resources */ = { + 7DC3FAEEEC64FFC78D855373 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "📦 Copy Pods Resources"; + name = "[CP] Copy Pods Resources"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; @@ -1125,29 +1184,44 @@ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Google/Pods-Google-resources.sh\"\n"; showEnvVarsInLog = 0; }; - D17D52CDF1B7F0B80C57DD59 /* 📦 Check Pods Manifest.lock */ = { + B4BB8A5983D129F23E140373 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "📦 Check Pods Manifest.lock"; + name = "[CP] Embed Pods Frameworks"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-FirebaseUITests/Pods-FirebaseUITests-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + CC1F8BDF974CFA17969663CA /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Copy Pods Resources"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-FirebaseUITests/Pods-FirebaseUITests-resources.sh\"\n"; showEnvVarsInLog = 0; }; - D1854EF2AF0C2648CD5C535D /* 📦 Check Pods Manifest.lock */ = { + D17D52CDF1B7F0B80C57DD59 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "📦 Check Pods Manifest.lock"; + name = "[CP] Check Pods Manifest.lock"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; @@ -1155,28 +1229,29 @@ shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; showEnvVarsInLog = 0; }; - D8B6ADA21B58DB8B005CDDB2 /* Build Framework */ = { + D1854EF2AF0C2648CD5C535D /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "Build Framework"; + name = "[CP] Check Pods Manifest.lock"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "set -e\n\nexport FRAMEWORK_LOCN=\"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.framework\"\n\n# Create the path to the real Headers die\nmkdir -p \"${FRAMEWORK_LOCN}/Versions/A/Headers\"\n\n# Create the required symlinks\n/bin/ln -sfh A \"${FRAMEWORK_LOCN}/Versions/Current\"\n/bin/ln -sfh Versions/Current/Headers \"${FRAMEWORK_LOCN}/Headers\"\n/bin/ln -sfh \"Versions/Current/${PRODUCT_NAME}\" \\\n\"${FRAMEWORK_LOCN}/${PRODUCT_NAME}\"\n\n# Copy the public headers into the framework\n/bin/cp -a \"${TARGET_BUILD_DIR}/${PUBLIC_HEADERS_FOLDER_PATH}/\" \\\n\"${FRAMEWORK_LOCN}/Versions/A/Headers\""; + shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; + showEnvVarsInLog = 0; }; - EDF383BBC4F3A6CDD217F06A /* 📦 Copy Pods Resources */ = { + EDF383BBC4F3A6CDD217F06A /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "📦 Copy Pods Resources"; + name = "[CP] Copy Pods Resources"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; @@ -1184,14 +1259,14 @@ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Database/Pods-Database-resources.sh\"\n"; showEnvVarsInLog = 0; }; - EE4FB8B0A2310732DCF60111 /* 📦 Check Pods Manifest.lock */ = { + EE4FB8B0A2310732DCF60111 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "📦 Check Pods Manifest.lock"; + name = "[CP] Check Pods Manifest.lock"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; @@ -1202,6 +1277,17 @@ /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 8D2CB5D91D53F5AE0097FEEB /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 8DD3AACD1D5942C900E6B1F9 /* FirebaseArrayTestUtils.m in Sources */, + 8DD498F41D5BAAA30022D37E /* FirebaseTableViewDataSourceTest.m in Sources */, + 8DD3AACA1D5940E000E6B1F9 /* FirebaseCollectionViewDataSourceTest.m in Sources */, + 8D2CB5E91D53F5C50097FEEB /* FirebaseArrayTest.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; D8666CAA1CEAFC93002B7C26 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -1258,6 +1344,7 @@ 1073FE8F1CE509EC00E2F73F /* FIRAuthUITableViewCell.m in Sources */, 1073FE981CE509EC00E2F73F /* FIRPasswordRecoveryViewController.m in Sources */, 1073FEA11CE509EC00E2F73F /* FIRPasswordVerificationViewController.m in Sources */, + 8DFCD1381D53FE8D00208D7B /* FIRAuthUISignInButton.m in Sources */, 1073FE951CE509EC00E2F73F /* FIREmailEntryViewController.m in Sources */, 1073FEB71CE50A8F00E2F73F /* FirebaseDataSource.m in Sources */, 1073FE6B1CE509EC00E2F73F /* FIRFacebookAuthUI.m in Sources */, @@ -1280,6 +1367,11 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ + 8D2CB5E41D53F5AE0097FEEB /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = D8C579A51B57349000899F86 /* FirebaseUI */; + targetProxy = 8D2CB5E31D53F5AE0097FEEB /* PBXContainerItemProxy */; + }; D8666D551CEB036C002B7C26 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = D8666CC51CEAFD40002B7C26 /* Auth */; @@ -1320,9 +1412,38 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ + 8D2CB5E51D53F5AE0097FEEB /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_NONNULL = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + INFOPLIST_FILE = FirebaseUITests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 9.3; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.firebase.FirebaseUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 8D2CB5E61D53F5AE0097FEEB /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_NONNULL = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + INFOPLIST_FILE = FirebaseUITests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 9.3; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.firebase.FirebaseUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; D8666CB41CEAFC93002B7C26 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = B06216F85B72D25E7A394865 /* Pods-Database.debug.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; @@ -1335,7 +1456,10 @@ "$(inherited)", ); IPHONEOS_DEPLOYMENT_TARGET = 9.3; - OTHER_LDFLAGS = "-ObjC"; + OTHER_LDFLAGS = ( + "$(inherited)", + "-ObjC", + ); PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; }; @@ -1343,7 +1467,6 @@ }; D8666CB51CEAFC93002B7C26 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = A82808FA5C9B636BDCD554A1 /* Pods-Database.release.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; @@ -1352,7 +1475,10 @@ "$(PROJECT_DIR)/Pods/FirebaseDatabase/Frameworks", ); IPHONEOS_DEPLOYMENT_TARGET = 9.3; - OTHER_LDFLAGS = "-ObjC"; + OTHER_LDFLAGS = ( + "$(inherited)", + "-ObjC", + ); PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; }; @@ -1360,7 +1486,6 @@ }; D8666CCD1CEAFD40002B7C26 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 9DC5493B51C2AA187A0CADC3 /* Pods-Auth.debug.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; @@ -1379,7 +1504,10 @@ "$(inherited)", ); IPHONEOS_DEPLOYMENT_TARGET = 9.3; - OTHER_LDFLAGS = "-ObjC"; + OTHER_LDFLAGS = ( + "$(inherited)", + "-ObjC", + ); PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; }; @@ -1387,7 +1515,6 @@ }; D8666CCE1CEAFD40002B7C26 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 0A1917FE3607B78B018B843D /* Pods-Auth.release.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; @@ -1402,7 +1529,10 @@ "$(PROJECT_DIR)/Pods/FirebaseInstanceID/Frameworks", ); IPHONEOS_DEPLOYMENT_TARGET = 9.3; - OTHER_LDFLAGS = "-ObjC"; + OTHER_LDFLAGS = ( + "$(inherited)", + "-ObjC", + ); PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; }; @@ -1410,7 +1540,6 @@ }; D8666D0D1CEAFF29002B7C26 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 6A47DCC5F13C82AD936429CD /* Pods-Facebook.debug.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; @@ -1419,7 +1548,10 @@ "$(inherited)", ); IPHONEOS_DEPLOYMENT_TARGET = 9.3; - OTHER_LDFLAGS = "-ObjC"; + OTHER_LDFLAGS = ( + "$(inherited)", + "-ObjC", + ); PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; }; @@ -1427,12 +1559,14 @@ }; D8666D0E1CEAFF29002B7C26 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 51FE96CCEB38B9896FA8763F /* Pods-Facebook.release.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; IPHONEOS_DEPLOYMENT_TARGET = 9.3; - OTHER_LDFLAGS = "-ObjC"; + OTHER_LDFLAGS = ( + "$(inherited)", + "-ObjC", + ); PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; }; @@ -1440,7 +1574,6 @@ }; D8666D1B1CEAFF41002B7C26 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = D6A4679357D8650D724CF886 /* Pods-Google.debug.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; @@ -1453,7 +1586,10 @@ "$(inherited)", ); IPHONEOS_DEPLOYMENT_TARGET = 9.3; - OTHER_LDFLAGS = "-ObjC"; + OTHER_LDFLAGS = ( + "$(inherited)", + "-ObjC", + ); PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; }; @@ -1461,7 +1597,6 @@ }; D8666D1C1CEAFF41002B7C26 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = A19CDD03BA9195E469EC5BAB /* Pods-Google.release.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; @@ -1470,7 +1605,10 @@ "$(PROJECT_DIR)/Pods/GoogleSignIn/Frameworks", ); IPHONEOS_DEPLOYMENT_TARGET = 9.3; - OTHER_LDFLAGS = "-ObjC"; + OTHER_LDFLAGS = ( + "$(inherited)", + "-ObjC", + ); PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; }; @@ -1497,6 +1635,7 @@ DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + FRAMEWORK_SEARCH_PATHS = "$(inherited)"; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -1516,6 +1655,7 @@ MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; + TEST_HOST = ""; }; name = Debug; }; @@ -1540,6 +1680,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + FRAMEWORK_SEARCH_PATHS = "$(inherited)"; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -1551,13 +1692,13 @@ IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; + TEST_HOST = ""; VALIDATE_PRODUCT = YES; }; name = Release; }; D8C579B01B57349000899F86 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = D8199F2217B396C2D50E9224 /* Pods-FirebaseUI.debug.xcconfig */; buildSettings = { CLANG_MODULES_AUTOLINK = YES; DEAD_CODE_STRIPPING = NO; @@ -1590,7 +1731,7 @@ "$(PROJECT_DIR)/target/Products/Release-iphonesimulator", "$(PROJECT_DIR)/sdk/02468137448ba914-Google-1.0.7/Libraries", ); - OTHER_LDFLAGS = ""; + OTHER_LDFLAGS = "$(inherited)"; PRODUCT_NAME = "$(TARGET_NAME)"; PUBLIC_HEADERS_FOLDER_PATH = "include/$(PROJECT_NAME)"; SKIP_INSTALL = YES; @@ -1600,7 +1741,6 @@ }; D8C579B11B57349000899F86 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 38A0F57C03EF230E5A720BD1 /* Pods-FirebaseUI.release.xcconfig */; buildSettings = { CLANG_MODULES_AUTOLINK = YES; DEAD_CODE_STRIPPING = NO; @@ -1632,7 +1772,7 @@ "$(PROJECT_DIR)/target/Products/Release-iphonesimulator", "$(PROJECT_DIR)/sdk/02468137448ba914-Google-1.0.7/Libraries", ); - OTHER_LDFLAGS = ""; + OTHER_LDFLAGS = "$(inherited)"; PRODUCT_NAME = "$(TARGET_NAME)"; PUBLIC_HEADERS_FOLDER_PATH = "include/$(PROJECT_NAME)"; SKIP_INSTALL = YES; @@ -1643,6 +1783,15 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 8D2CB5E71D53F5AE0097FEEB /* Build configuration list for PBXNativeTarget "FirebaseUITests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 8D2CB5E51D53F5AE0097FEEB /* Debug */, + 8D2CB5E61D53F5AE0097FEEB /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; D8666CB61CEAFC93002B7C26 /* Build configuration list for PBXNativeTarget "Database" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/FirebaseUI/Auth/AuthProviderUI/Facebook/Source/FIRFacebookAuthUI.h b/FirebaseUI/Auth/AuthProviderUI/Facebook/Source/FIRFacebookAuthUI.h index 2eed3c50118..c11c2eeee31 100644 --- a/FirebaseUI/Auth/AuthProviderUI/Facebook/Source/FIRFacebookAuthUI.h +++ b/FirebaseUI/Auth/AuthProviderUI/Facebook/Source/FIRFacebookAuthUI.h @@ -28,24 +28,24 @@ NS_ASSUME_NONNULL_BEGIN /** @property appId @brief The Facebook App ID. */ -@property(nonatomic, copy, readonly) NSString *appID; +@property(nonatomic, readonly, copy) NSString *appID; /** @property scopes @brief The scopes to use with Facebook Login. @remarks Defaults to using "email" scopes. */ -@property(nonatomic, copy) NSArray *scopes; +@property(nonatomic, readonly, copy) NSArray *scopes; /** @fn init @brief Please use initWithAppId: */ -- (nullable instancetype)init NS_UNAVAILABLE; +- (instancetype)init NS_UNAVAILABLE; /** @fn initWithAppID: @brief Conevenience initializer. Uses a default permission of `@[ "email" ]`. @param appID The Facebook App ID. */ -- (nullable instancetype)initWithAppID:(NSString *)appID; +- (instancetype)initWithAppID:(NSString *)appID; /** @fn initWithAppID:permissions: @brief Designated initializer. @@ -53,7 +53,7 @@ NS_ASSUME_NONNULL_BEGIN @param permissions The permissions of the app. This array must be an array of specific string values as defined in https://developers.facebook.com/docs/facebook-login/permissions/ */ -- (nullable instancetype)initWithAppID:(NSString *)appID permissions:(NSArray *)permissions NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithAppID:(NSString *)appID permissions:(NSArray *)permissions NS_DESIGNATED_INITIALIZER; @end diff --git a/FirebaseUI/Auth/AuthProviderUI/Facebook/Source/FIRFacebookAuthUI.m b/FirebaseUI/Auth/AuthProviderUI/Facebook/Source/FIRFacebookAuthUI.m index 2193e0b6b50..f79a4622c18 100644 --- a/FirebaseUI/Auth/AuthProviderUI/Facebook/Source/FIRFacebookAuthUI.m +++ b/FirebaseUI/Auth/AuthProviderUI/Facebook/Source/FIRFacebookAuthUI.m @@ -49,13 +49,13 @@ @implementation FIRFacebookAuthUI { FIRAuthProviderSignInCompletionBlock _pendingSignInCallback; } -- (nullable instancetype)init { +- (instancetype)init { @throw [NSException exceptionWithName:@"Attempt to call unavailable initializer." reason:@"Please call the designated initializer." userInfo:nil]; } -- (nullable instancetype)initWithAppID:(NSString *)appID permissions:(NSArray *)permissions { +- (instancetype)initWithAppID:(NSString *)appID permissions:(NSArray *)permissions { self = [super init]; if (self != nil) { _scopes = permissions; @@ -65,7 +65,7 @@ - (nullable instancetype)initWithAppID:(NSString *)appID permissions:(NSArray *) return self; } -- (nullable instancetype)initWithAppID:(NSString *)appID { +- (instancetype)initWithAppID:(NSString *)appID { return [self initWithAppID:appID permissions:@[ @"email" ]]; } diff --git a/FirebaseUI/Auth/AuthProviderUI/Google/Source/FIRGoogleAuthUI.h b/FirebaseUI/Auth/AuthProviderUI/Google/Source/FIRGoogleAuthUI.h index 35aed7ca956..21e2b821660 100644 --- a/FirebaseUI/Auth/AuthProviderUI/Google/Source/FIRGoogleAuthUI.h +++ b/FirebaseUI/Auth/AuthProviderUI/Google/Source/FIRGoogleAuthUI.h @@ -32,20 +32,30 @@ NS_ASSUME_NONNULL_BEGIN /** @property scopes @brief The scopes to use with Google Sign In. - @remarks Defaults to using "email" and "profile" scopes. + @remarks Defaults to using email and profile scopes. For a list of all scopes + see https://developers.google.com/identity/protocols/googlescopes */ -@property(nonatomic, copy) NSArray *scopes; +@property(nonatomic, copy, readonly) NSArray *scopes; /** @fn init @brief Please use initWithClientId: */ -- (nullable instancetype)init NS_UNAVAILABLE; +- (instancetype)init NS_UNAVAILABLE; /** @fn initWithClientID: - @brief Designated initializer. + @brief Convenience initializer. Calls designated init with default + scopes of "email" and "profile". @param clientId The Google Sign In client ID. */ -- (nullable instancetype)initWithClientID:(NSString *)clientID NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithClientID:(NSString *)clientID; + +/** @fn initWithClientID:scopes: + @brief Designated initializer. + @param clientId The Google Sign In client ID. + @param scopes The user account scopes required by the app. A list of possible scopes can be + found at https://developers.google.com/identity/protocols/googlescopes + */ +- (instancetype)initWithClientID:(NSString *)clientID scopes:(NSArray *)scopes NS_DESIGNATED_INITIALIZER; @end diff --git a/FirebaseUI/Auth/AuthProviderUI/Google/Source/FIRGoogleAuthUI.m b/FirebaseUI/Auth/AuthProviderUI/Google/Source/FIRGoogleAuthUI.m index 5bc73adc637..8df0d26b6e3 100644 --- a/FirebaseUI/Auth/AuthProviderUI/Google/Source/FIRGoogleAuthUI.m +++ b/FirebaseUI/Auth/AuthProviderUI/Google/Source/FIRGoogleAuthUI.m @@ -31,10 +31,15 @@ */ static NSString *const kGooglePlusMeScope = @"https://www.googleapis.com/auth/plus.me"; -/** @var kGooglePlusScopesPrefix - @brief The OAuth scope string prefix for all scopes starting with "plus.". +/** @var kGooglePlusMeScope + @brief The OAuth scope string for the user's email scope. + */ +static NSString *const kGoogleUserInfoEmailScope = @"https://www.googleapis.com/auth/userinfo.email"; + +/** @var kGooglePlusMeScope + @brief The OAuth scope string for the basic G+ profile information scope. */ -static NSString *const kGooglePlusScopesPrefix = @"https://www.googleapis.com/auth/plus."; +static NSString *const kGoogleUserInfoProfileScope = @"https://www.googleapis.com/auth/userinfo.profile"; /** @var kTableName @brief The name of the strings table to search for localized strings. @@ -60,17 +65,22 @@ @implementation FIRGoogleAuthUI { FIRAuthProviderSignInCompletionBlock _pendingSignInCallback; } -- (nullable instancetype)init { +- (instancetype)init { @throw [NSException exceptionWithName:@"Attempt to call unavailable initializer." reason:@"Please call the designated initializer." userInfo:nil]; } -- (nullable instancetype)initWithClientID:(NSString *)clientID { +- (instancetype)initWithClientID:(NSString *)clientID { + return [self initWithClientID:clientID + scopes:@[kGoogleUserInfoEmailScope, kGoogleUserInfoProfileScope]]; +} + +- (instancetype)initWithClientID:(NSString *)clientID scopes:(NSArray *)scopes { self = [super init]; if (self) { _clientID = [clientID copy]; - _scopes = @[ @"email", @"profile" ]; + _scopes = [scopes copy]; } return self; } diff --git a/FirebaseUI/Auth/AuthUI/Source/FIRAuthPickerViewController.xib b/FirebaseUI/Auth/AuthUI/Source/FIRAuthPickerViewController.xib index c69f57db814..e4cb689120c 100644 --- a/FirebaseUI/Auth/AuthUI/Source/FIRAuthPickerViewController.xib +++ b/FirebaseUI/Auth/AuthUI/Source/FIRAuthPickerViewController.xib @@ -1,7 +1,6 @@ - + - diff --git a/FirebaseUI/Auth/AuthUI/Source/FIRAuthUI.h b/FirebaseUI/Auth/AuthUI/Source/FIRAuthUI.h index 52d5660ddca..5e2c774c527 100644 --- a/FirebaseUI/Auth/AuthUI/Source/FIRAuthUI.h +++ b/FirebaseUI/Auth/AuthUI/Source/FIRAuthUI.h @@ -67,7 +67,7 @@ typedef void (^FIRAuthUIResultCallback)(FIRUser *_Nullable user, NSError *_Nulla @brief Gets the @c FIRAuthUI object for the default FirebaseApp. @remarks Thread safe. */ -+ (nullable FIRAuthUI *)authUI NS_SWIFT_NAME(authUI()); ++ (nullable FIRAuthUI *)defaultAuthUI; /** @fn authUIWithAuth: @brief Gets the @c FIRAuthUI instance for a @c FIRAuth. diff --git a/FirebaseUI/Auth/AuthUI/Source/FIRAuthUI.m b/FirebaseUI/Auth/AuthUI/Source/FIRAuthUI.m index c230fade4fd..c2df3ce66b8 100644 --- a/FirebaseUI/Auth/AuthUI/Source/FIRAuthUI.m +++ b/FirebaseUI/Auth/AuthUI/Source/FIRAuthUI.m @@ -45,7 +45,7 @@ - (nullable instancetype)initWithAuth:(FIRAuth *)auth NS_DESIGNATED_INITIALIZER; @implementation FIRAuthUI -+ (nullable FIRAuthUI *)authUI { ++ (nullable FIRAuthUI *)defaultAuthUI { FIRAuth *defaultAuth = [FIRAuth auth]; if (!defaultAuth) { return nil; @@ -54,6 +54,7 @@ + (nullable FIRAuthUI *)authUI { } + (nullable FIRAuthUI *)authUIWithAuth:(FIRAuth *)auth { + NSParameterAssert(auth != nil); @synchronized (self) { // Let the FIRAuth instance retain the FIRAuthUI instance. FIRAuthUI *authUI = objc_getAssociatedObject(auth, &kAuthAssociationKey); @@ -66,7 +67,7 @@ + (nullable FIRAuthUI *)authUIWithAuth:(FIRAuth *)auth { } } -- (nullable instancetype)initWithAuth:(FIRAuth *)auth { +- (instancetype)initWithAuth:(FIRAuth *)auth { self = [super init]; if (self) { _auth = auth; diff --git a/FirebaseUI/Auth/AuthUI/Source/FIRAuthUIStrings.m b/FirebaseUI/Auth/AuthUI/Source/FIRAuthUIStrings.m index 3268b63adf3..609c73a8d29 100644 --- a/FirebaseUI/Auth/AuthUI/Source/FIRAuthUIStrings.m +++ b/FirebaseUI/Auth/AuthUI/Source/FIRAuthUIStrings.m @@ -75,7 +75,7 @@ @implementation FIRAuthUIStrings @return Localized value of the string identified by the key. */ + (NSString *)localizedStringForKey:(nonnull NSString *)key { - NSBundle *customStringsBundle = [FIRAuthUI authUI].customStringsBundle; + NSBundle *customStringsBundle = [FIRAuthUI defaultAuthUI].customStringsBundle; if (customStringsBundle) { NSString *localizedString = [customStringsBundle localizedStringForKey:key value:kKeyNotFound diff --git a/FirebaseUI/Database/API/FirebaseArray.h b/FirebaseUI/Database/API/FirebaseArray.h index c52d03b75be..1787ec2c311 100644 --- a/FirebaseUI/Database/API/FirebaseArray.h +++ b/FirebaseUI/Database/API/FirebaseArray.h @@ -18,15 +18,25 @@ // clang-format on -#import +@import Firebase; #import "FirebaseArrayDelegate.h" NS_ASSUME_NONNULL_BEGIN -@class FIRDatabaseQuery; -@class FIRDatabaseReference; -@class FIRDataSnapshot; +@protocol FIRDataObservable +@required + +- (FIRDatabaseHandle)observeEventType:(FIRDataEventType)eventType + andPreviousSiblingKeyWithBlock:(void (^)(FIRDataSnapshot *snapshot, NSString *__nullable prevKey))block + withCancelBlock:(nullable void (^)(NSError* error))cancelBlock; + +- (void)removeObserverWithHandle:(FIRDatabaseHandle)handle; + +@end + +@interface FIRDatabaseQuery (FIRDataObservable) +@end /** * FirebaseArray provides an array structure that is synchronized with a Firebase reference or @@ -39,43 +49,36 @@ NS_ASSUME_NONNULL_BEGIN * The delegate object that array changes are surfaced to, which conforms to the * [FirebaseArrayDelegate Protocol](FirebaseArrayDelegate). */ -@property(weak, nonatomic) id delegate; +@property(weak, nonatomic, nullable) id delegate; /** * The query on a Firebase reference that provides data to populate the instance of FirebaseArray. */ -@property(strong, nonatomic) FIRDatabaseQuery *query; +@property(strong, nonatomic) id query; /** - * The delegate object that array changes are surfaced to. + * The number of objects in the FirebaseArray. */ -@property(strong, nonatomic) NSMutableArray * snapshots; - -#pragma mark - -#pragma mark Initializer methods +@property(nonatomic, readonly) NSUInteger count; /** - * Intitalizes FirebaseArray with a standard Firebase reference. - * @param ref The Firebase reference which provides data to FirebaseArray - * @return The instance of FirebaseArray + * The items currently in the FirebaseArray. */ -- (instancetype)initWithRef:(FIRDatabaseReference *)ref; +@property(nonatomic, readonly, copy) NSArray *items; + +#pragma mark - Initializer methods /** - * Intitalizes FirebaseArray with a Firebase query (FIRDatabaseQuery). - * @param query A query on a Firebase reference which provides filtered data to FirebaseArray - * @return The instance of FirebaseArray + * Initalizes FirebaseArray with a Firebase query (FIRDatabaseQuery) or database reference + * (FIRDatabaseReference). + * @param query A query or Firebase database reference + * @return A FirebaseArray instance */ -- (instancetype)initWithQuery:(FIRDatabaseQuery *)query; +- (instancetype)initWithQuery:(id)query NS_DESIGNATED_INITIALIZER; -#pragma mark - -#pragma mark Public API methods +- (instancetype)init NS_UNAVAILABLE; -/** - * Returns the count of objects in the FirebaseArray. - * @return The count of objects in the FirebaseArray - */ -- (NSUInteger)count; +#pragma mark - Public API methods /** * Returns an object at a specific index in the FirebaseArray. @@ -100,21 +103,16 @@ NS_ASSUME_NONNULL_BEGIN /** * Support for subscripting. This method is unused and trying to write directly to the - * array using subscripting will cause an assertion. + * array using subscripting will cause an assertion failure. */ -- (void)setObject:(id)obj atIndexedSubscript:(NSUInteger)idx; - -#pragma mark - -#pragma mark Private API methods +- (void)setObject:(id)obj atIndexedSubscript:(NSUInteger)idx NS_UNAVAILABLE; /** * Returns an index for a given object's key (that matches the object's key in the corresponding * Firebase reference). * @param key The key of the desired object - * @return The index of the object for which the key matches or -1 if the key is null - * @exception FirebaseArrayKeyNotFoundException Thrown when the desired key is not in the - * FirebaseArray, likely indicating that the FirebaseArray is no longer being properly synchronized - * with the Firebase database. + * @return The index of the object for which the key matches or NSNotFound if the key is not found + * @exception NSInvalidArgumentException Thrown when the `key` parameter is `nil`. */ - (NSUInteger)indexForKey:(NSString *)key; diff --git a/FirebaseUI/Database/API/FirebaseArrayDelegate.h b/FirebaseUI/Database/API/FirebaseArrayDelegate.h index 731755d193b..785b4f952e5 100644 --- a/FirebaseUI/Database/API/FirebaseArrayDelegate.h +++ b/FirebaseUI/Database/API/FirebaseArrayDelegate.h @@ -18,6 +18,8 @@ // clang-format on +@class FirebaseArray; + /** * A protocol to allow instances of FirebaseArray to raise events through a * delegate. Raises all @@ -36,7 +38,7 @@ * @param object The object added to the FirebaseArray * @param index The index the child was added at */ -- (void)childAdded:(id)object atIndex:(NSUInteger)index; +- (void)array:(FirebaseArray *)array didAddObject:(id)object atIndex:(NSUInteger)index; /** * Delegate method which is called whenever an object is chinged in a @@ -47,7 +49,7 @@ * @param object The object that changed in the FirebaseArray * @param index The index the child was changed at */ -- (void)childChanged:(id)object atIndex:(NSUInteger)index; +- (void)array:(FirebaseArray *)array didChangeObject:(id)object atIndex:(NSUInteger)index; /** * Delegate method which is called whenever an object is removed from a @@ -58,7 +60,7 @@ * @param object The object removed from the FirebaseArray * @param index The index the child was removed at */ -- (void)childRemoved:(id)object atIndex:(NSUInteger)index; +- (void)array:(FirebaseArray *)array didRemoveObject:(id)object atIndex:(NSUInteger)index; /** * Delegate method which is called whenever an object is moved within a @@ -70,12 +72,12 @@ * @param fromIndex The index the child is being moved from * @param toIndex The index the child is being moved to */ -- (void)childMoved:(id)object fromIndex:(NSUInteger)fromIndex toIndex:(NSUInteger)toIndex; +- (void)array:(FirebaseArray *)array didMoveObject:(id)object fromIndex:(NSUInteger)fromIndex toIndex:(NSUInteger)toIndex; /** * Delegate method which is called whenever the backing query is canceled. * @param error the error that was raised */ -- (void)canceledWithError:(NSError *)error; +- (void)array:(FirebaseArray *)array queryCancelledWithError:(NSError *)error; @end diff --git a/FirebaseUI/Database/API/FirebaseCollectionViewDataSource.h b/FirebaseUI/Database/API/FirebaseCollectionViewDataSource.h index 988817192dd..3b12b264942 100644 --- a/FirebaseUI/Database/API/FirebaseCollectionViewDataSource.h +++ b/FirebaseUI/Database/API/FirebaseCollectionViewDataSource.h @@ -52,8 +52,9 @@ NS_ASSUME_NONNULL_BEGIN * to [Message class] in Obj-C or Message.self in Swift, then objects of type * Message will be * returned instead of type FIRDataSnapshot. + * Defaults to FIRDataSnapshot. */ -@property(strong, nonatomic) Class modelClass; +@property(strong, nonatomic, null_resettable) Class modelClass; /** * The cell class to coerce UICollectionViewCells to (if desired). For instance, @@ -62,8 +63,9 @@ NS_ASSUME_NONNULL_BEGIN * in Swift, then * objects of type CustomCollectionViewCell will be returned instead of type * UICollectionViewCell. + * Defaults to UICollectionViewCell. */ -@property(strong, nonatomic) Class cellClass; +@property(strong, nonatomic, null_resettable) Class cellClass; /** * The reuse identifier for cells in the UICollectionView. @@ -89,7 +91,7 @@ NS_ASSUME_NONNULL_BEGIN * provided by the * datasource. */ -@property(strong, nonatomic) void (^populateCell) +@property(strong, nonatomic, readonly) void (^populateCell) (__kindof UICollectionViewCell *cell, __kindof NSObject *object); /** @@ -104,8 +106,8 @@ NS_ASSUME_NONNULL_BEGIN * FIRDataSnapshots */ - (instancetype)initWithRef:(FIRDatabaseReference *)ref - cellReuseIdentifier:(NSString *)identifier - view:(UICollectionView *)collectionView; + cellReuseIdentifier:(NSString *)identifier + view:(UICollectionView *)collectionView; /** * Initialize an instance of FirebaseCollectionViewDataSource that populates @@ -121,8 +123,8 @@ NS_ASSUME_NONNULL_BEGIN * FIRDataSnapshots */ - (instancetype)initWithRef:(FIRDatabaseReference *)ref - prototypeReuseIdentifier:(NSString *)identifier - view:(UICollectionView *)collectionView; + prototypeReuseIdentifier:(NSString *)identifier + view:(UICollectionView *)collectionView; /** * Initialize an instance of FirebaseCollectionViewDataSource that populates a @@ -139,9 +141,9 @@ NS_ASSUME_NONNULL_BEGIN * UICollectionViewCell with FIRDataSnapshots */ - (instancetype)initWithRef:(FIRDatabaseReference *)ref - cellClass:(nullable Class)cell - cellReuseIdentifier:(NSString *)identifier - view:(UICollectionView *)collectionView; + cellClass:(nullable Class)cell + cellReuseIdentifier:(NSString *)identifier + view:(UICollectionView *)collectionView; /** * Initialize an instance of FirebaseCollectionViewDataSource that populates a @@ -157,9 +159,9 @@ NS_ASSUME_NONNULL_BEGIN * FIRDataSnapshots */ - (instancetype)initWithRef:(FIRDatabaseReference *)ref - nibNamed:(NSString *)nibName - cellReuseIdentifier:(NSString *)identifier - view:(UICollectionView *)collectionView; + nibNamed:(NSString *)nibName + cellReuseIdentifier:(NSString *)identifier + view:(UICollectionView *)collectionView; /** * Initialize an instance of FirebaseCollectionViewDataSource that populates @@ -175,9 +177,9 @@ NS_ASSUME_NONNULL_BEGIN * a custom model class */ - (instancetype)initWithRef:(FIRDatabaseReference *)ref - modelClass:(nullable Class)model - cellReuseIdentifier:(NSString *)identifier - view:(UICollectionView *)collectionView; + modelClass:(nullable Class)model + cellReuseIdentifier:(NSString *)identifier + view:(UICollectionView *)collectionView; /** * Initialize an instance of FirebaseCollectionViewDataSource that populates @@ -195,9 +197,9 @@ NS_ASSUME_NONNULL_BEGIN * a custom model class */ - (instancetype)initWithRef:(FIRDatabaseReference *)ref - modelClass:(nullable Class)model - prototypeReuseIdentifier:(NSString *)identifier - view:(UICollectionView *)collectionView; + modelClass:(nullable Class)model + prototypeReuseIdentifier:(NSString *)identifier + view:(UICollectionView *)collectionView; /** * Initialize an instance of FirebaseCollectionViewDataSource that populates a @@ -216,10 +218,10 @@ NS_ASSUME_NONNULL_BEGIN * UICollectionViewCell with a custom model class */ - (instancetype)initWithRef:(FIRDatabaseReference *)ref - modelClass:(nullable Class)model - cellClass:(nullable Class)cell - cellReuseIdentifier:(NSString *)identifier - view:(UICollectionView *)collectionView; + modelClass:(nullable Class)model + cellClass:(nullable Class)cell + cellReuseIdentifier:(NSString *)identifier + view:(UICollectionView *)collectionView; /** * Initialize an instance of FirebaseCollectionViewDataSource that populates a @@ -237,10 +239,10 @@ NS_ASSUME_NONNULL_BEGIN * model class */ - (instancetype)initWithRef:(FIRDatabaseReference *)ref - modelClass:(nullable Class)model - nibNamed:(NSString *)nibName - cellReuseIdentifier:(NSString *)identifier - view:(UICollectionView *)collectionView; + modelClass:(nullable Class)model + nibNamed:(NSString *)nibName + cellReuseIdentifier:(NSString *)identifier + view:(UICollectionView *)collectionView; /** * Initialize an instance of FirebaseCollectionViewDataSource that populates @@ -254,8 +256,8 @@ NS_ASSUME_NONNULL_BEGIN * FIRDataSnapshots */ - (instancetype)initWithQuery:(FIRDatabaseQuery *)query - cellReuseIdentifier:(NSString *)identifier - view:(UICollectionView *)collectionView; + cellReuseIdentifier:(NSString *)identifier + view:(UICollectionView *)collectionView; /** * Initialize an instance of FirebaseCollectionViewDataSource that populates @@ -271,8 +273,8 @@ NS_ASSUME_NONNULL_BEGIN * FIRDataSnapshots */ - (instancetype)initWithQuery:(FIRDatabaseQuery *)query - prototypeReuseIdentifier:(NSString *)identifier - view:(UICollectionView *)collectionView; + prototypeReuseIdentifier:(NSString *)identifier + view:(UICollectionView *)collectionView; /** * Initialize an instance of FirebaseCollectionViewDataSource that populates a @@ -289,9 +291,9 @@ NS_ASSUME_NONNULL_BEGIN * UICollectionViewCell with FIRDataSnapshots */ - (instancetype)initWithQuery:(FIRDatabaseQuery *)query - cellClass:(nullable Class)cell - cellReuseIdentifier:(NSString *)identifier - view:(UICollectionView *)collectionView; + cellClass:(nullable Class)cell + cellReuseIdentifier:(NSString *)identifier + view:(UICollectionView *)collectionView; /** * Initialize an instance of FirebaseCollectionViewDataSource that populates a @@ -307,9 +309,9 @@ NS_ASSUME_NONNULL_BEGIN * FIRDataSnapshots */ - (instancetype)initWithQuery:(FIRDatabaseQuery *)query - nibNamed:(NSString *)nibName - cellReuseIdentifier:(NSString *)identifier - view:(UICollectionView *)collectionView; + nibNamed:(NSString *)nibName + cellReuseIdentifier:(NSString *)identifier + view:(UICollectionView *)collectionView; /** * Initialize an instance of FirebaseCollectionViewDataSource that populates @@ -325,9 +327,9 @@ NS_ASSUME_NONNULL_BEGIN * a custom model class */ - (instancetype)initWithQuery:(FIRDatabaseQuery *)query - modelClass:(nullable Class)model - cellReuseIdentifier:(NSString *)identifier - view:(UICollectionView *)collectionView; + modelClass:(nullable Class)model + cellReuseIdentifier:(NSString *)identifier + view:(UICollectionView *)collectionView; /** * Initialize an instance of FirebaseCollectionViewDataSource that populates @@ -345,9 +347,9 @@ NS_ASSUME_NONNULL_BEGIN * a custom model class */ - (instancetype)initWithQuery:(FIRDatabaseQuery *)query - modelClass:(nullable Class)model - prototypeReuseIdentifier:(NSString *)identifier - view:(UICollectionView *)collectionView; + modelClass:(nullable Class)model + prototypeReuseIdentifier:(NSString *)identifier + view:(UICollectionView *)collectionView; /** * Initialize an instance of FirebaseCollectionViewDataSource that populates a @@ -366,10 +368,10 @@ NS_ASSUME_NONNULL_BEGIN * UICollectionViewCell with a custom model class */ - (instancetype)initWithQuery:(FIRDatabaseQuery *)query - modelClass:(nullable Class)model - cellClass:(nullable Class)cell - cellReuseIdentifier:(NSString *)identifier - view:(UICollectionView *)collectionView; + modelClass:(nullable Class)model + cellClass:(nullable Class)cell + cellReuseIdentifier:(NSString *)identifier + view:(UICollectionView *)collectionView; /** * Initialize an instance of FirebaseCollectionViewDataSource that populates a @@ -387,10 +389,10 @@ NS_ASSUME_NONNULL_BEGIN * model class */ - (instancetype)initWithQuery:(FIRDatabaseQuery *)query - modelClass:(nullable Class)model - nibNamed:(NSString *)nibName - cellReuseIdentifier:(NSString *)identifier - view:(UICollectionView *)collectionView; + modelClass:(nullable Class)model + nibNamed:(NSString *)nibName + cellReuseIdentifier:(NSString *)identifier + view:(UICollectionView *)collectionView; /** * This method populates the fields of a UICollectionViewCell or subclass given @@ -400,9 +402,8 @@ NS_ASSUME_NONNULL_BEGIN * subclass) and the * corresponding object to populate the cell with. */ -- (void)populateCellWithBlock: - (void (^)(__kindof UICollectionViewCell *cell, - __kindof NSObject *object))callback; +- (void)populateCellWithBlock:(void (^)(__kindof UICollectionViewCell *cell, + __kindof NSObject *object))callback; @end diff --git a/FirebaseUI/Database/API/FirebaseDataSource.h b/FirebaseUI/Database/API/FirebaseDataSource.h index b91a551674a..4bc5d19cb34 100644 --- a/FirebaseUI/Database/API/FirebaseDataSource.h +++ b/FirebaseUI/Database/API/FirebaseDataSource.h @@ -35,16 +35,17 @@ @interface FirebaseDataSource : NSObject /** - * The FirebaseArray which backs the instance of the datasource. + * The items in the data source. */ -@property(strong, nonatomic) FirebaseArray *array; - -- (instancetype)initWithArray:(FirebaseArray *)array; +@property (nonatomic, readonly, copy) NSArray *items; /** * Pass through of [FirebaseArray count]. */ -- (NSUInteger)count; +@property (nonatomic, readonly) NSUInteger count; + +- (instancetype)initWithArray:(FirebaseArray *)array NS_DESIGNATED_INITIALIZER; +- (instancetype)init NS_UNAVAILABLE; /** * Pass through of [FirebaseArray objectAtIndex:]. diff --git a/FirebaseUI/Database/API/FirebaseTableViewDataSource.h b/FirebaseUI/Database/API/FirebaseTableViewDataSource.h index d6968ff8955..6b840c71c81 100644 --- a/FirebaseUI/Database/API/FirebaseTableViewDataSource.h +++ b/FirebaseUI/Database/API/FirebaseTableViewDataSource.h @@ -51,8 +51,9 @@ NS_ASSUME_NONNULL_BEGIN * to [Message class] in Obj-C or Message.self in Swift, then objects of type * Message will be * returned instead of type FIRDataSnapshot. + * Defaults to FIRDataSnapshot. */ -@property(strong, nonatomic) Class modelClass; +@property(strong, nonatomic, null_resettable) Class modelClass; /** * The reuse identifier for cells in the UITableView. @@ -76,8 +77,8 @@ NS_ASSUME_NONNULL_BEGIN * The callback to populate a subclass of UITableViewCell with an object * provided by the datasource. */ -@property(strong, nonatomic) void (^populateCell) - (__kindof UITableViewCell *cell, __kindof NSObject *object); +@property(strong, nonatomic, readonly) void (^populateCell) + (__kindof UITableViewCell *cell,__kindof NSObject *object); /** * Initialize an instance of FirebaseTableViewDataSource that populates @@ -383,9 +384,8 @@ NS_ASSUME_NONNULL_BEGIN * This method populates the fields of a UITableViewCell or subclass given a * model object (or * FIRDataSnapshot). - * @param callback A block which returns an initialized UITableViewCell (or - * subclass) and the - * corresponding object to populate the cell with. + * @param callback A block which returns an initialized UITableViewCell + * (or subclass) and the corresponding object to populate the cell with. */ - (void)populateCellWithBlock:(void (^)(__kindof UITableViewCell *cell, __kindof NSObject *object))callback; diff --git a/FirebaseUI/Database/Implementation/FirebaseArray.m b/FirebaseUI/Database/Implementation/FirebaseArray.m index e566d642f2c..c63bb51f461 100755 --- a/FirebaseUI/Database/Implementation/FirebaseArray.m +++ b/FirebaseUI/Database/Implementation/FirebaseArray.m @@ -20,78 +20,107 @@ #import "FirebaseArray.h" +@interface FirebaseArray () + +/** + * The backing collection that holds all of the FirebaseArray's data. + */ +@property(strong, nonatomic) NSMutableArray *snapshots; + +/** + * A set containing the query observer handles that should be released when + * this array is freed. + */ +@property(strong, nonatomic) NSMutableSet *handles; + +@end + @import FirebaseDatabase; @implementation FirebaseArray -#pragma mark - -#pragma mark Initializer methods - -- (instancetype)initWithRef:(FIRDatabaseReference *)ref { - return [self initWithQuery:ref]; -} +#pragma mark - Initializer methods - (instancetype)initWithQuery:(FIRDatabaseQuery *)query { + NSParameterAssert(query != nil); self = [super init]; if (self) { self.snapshots = [NSMutableArray array]; self.query = query; + self.handles = [NSMutableSet setWithCapacity:4]; [self initListeners]; } return self; } -#pragma mark - -#pragma mark Memory management methods +#pragma mark - Memory management methods - (void)dealloc { - // TODO: Consider keeping track of these and only removing them if they are - // explicitly added here - [self.query removeAllObservers]; + for (NSNumber *handle in _handles) { + [_query removeObserverWithHandle:handle.unsignedIntegerValue]; + } } -#pragma mark - -#pragma mark Private API methods +#pragma mark - Private API methods - (void)initListeners { - [self.query observeEventType:FIRDataEventTypeChildAdded + FIRDatabaseHandle handle; + handle = [self.query observeEventType:FIRDataEventTypeChildAdded andPreviousSiblingKeyWithBlock:^(FIRDataSnapshot *snapshot, NSString *previousChildKey) { - NSUInteger index = [self indexForKey:previousChildKey] + 1; + NSUInteger index = 0; + if (previousChildKey != nil) { + index = [self indexForKey:previousChildKey] + 1; + } [self.snapshots insertObject:snapshot atIndex:index]; - [self.delegate childAdded:snapshot atIndex:index]; + if ([self.delegate respondsToSelector:@selector(array:didAddObject:atIndex:)]) { + [self.delegate array:self didAddObject:snapshot atIndex:index]; + } } withCancelBlock:^(NSError *error) { - [self.delegate canceledWithError:error]; + if ([self.delegate respondsToSelector:@selector(array:queryCancelledWithError:)]) { + [self.delegate array:self queryCancelledWithError:error]; + } }]; + [_handles addObject:@(handle)]; - [self.query observeEventType:FIRDataEventTypeChildChanged + handle = [self.query observeEventType:FIRDataEventTypeChildChanged andPreviousSiblingKeyWithBlock:^(FIRDataSnapshot *snapshot, NSString *previousChildKey) { NSUInteger index = [self indexForKey:snapshot.key]; [self.snapshots replaceObjectAtIndex:index withObject:snapshot]; - [self.delegate childChanged:snapshot atIndex:index]; + if ([self.delegate respondsToSelector:@selector(array:didChangeObject:atIndex:)]) { + [self.delegate array:self didChangeObject:snapshot atIndex:index]; + } } withCancelBlock:^(NSError *error) { - [self.delegate canceledWithError:error]; + if ([self.delegate respondsToSelector:@selector(array:queryCancelledWithError:)]) { + [self.delegate array:self queryCancelledWithError:error]; + } }]; + [_handles addObject:@(handle)]; - [self.query observeEventType:FIRDataEventTypeChildRemoved - withBlock:^(FIRDataSnapshot *snapshot) { + handle = [self.query observeEventType:FIRDataEventTypeChildRemoved + andPreviousSiblingKeyWithBlock:^(FIRDataSnapshot *snapshot, NSString *previousSiblingKey) { NSUInteger index = [self indexForKey:snapshot.key]; [self.snapshots removeObjectAtIndex:index]; - [self.delegate childRemoved:snapshot atIndex:index]; + if ([self.delegate respondsToSelector:@selector(array:didRemoveObject:atIndex:)]) { + [self.delegate array:self didRemoveObject:snapshot atIndex:index]; + } } withCancelBlock:^(NSError *error) { - [self.delegate canceledWithError:error]; + if ([self.delegate respondsToSelector:@selector(array:queryCancelledWithError:)]) { + [self.delegate array:self queryCancelledWithError:error]; + } }]; + [_handles addObject:@(handle)]; - [self.query observeEventType:FIRDataEventTypeChildMoved + handle = [self.query observeEventType:FIRDataEventTypeChildMoved andPreviousSiblingKeyWithBlock:^(FIRDataSnapshot *snapshot, NSString *previousChildKey) { NSUInteger fromIndex = [self indexForKey:snapshot.key]; [self.snapshots removeObjectAtIndex:fromIndex]; @@ -99,34 +128,34 @@ - (void)initListeners { NSUInteger toIndex = [self indexForKey:previousChildKey] + 1; [self.snapshots insertObject:snapshot atIndex:toIndex]; - [self.delegate childMoved:snapshot fromIndex:fromIndex toIndex:toIndex]; + if ([self.delegate respondsToSelector:@selector(array:didMoveObject:fromIndex:toIndex:)]) { + [self.delegate array:self didMoveObject:snapshot fromIndex:fromIndex toIndex:toIndex]; + } } withCancelBlock:^(NSError *error) { - [self.delegate canceledWithError:error]; + if ([self.delegate respondsToSelector:@selector(array:queryCancelledWithError:)]) { + [self.delegate array:self queryCancelledWithError:error]; + } }]; + [_handles addObject:@(handle)]; } - (NSUInteger)indexForKey:(NSString *)key { - if (!key) return -1; + NSParameterAssert(key != nil); for (NSUInteger index = 0; index < [self.snapshots count]; index++) { if ([key isEqualToString:[(FIRDataSnapshot *)[self.snapshots objectAtIndex:index] key]]) { return index; } } - - NSString *errorReason = - [NSString stringWithFormat:@"Key \"%@\" not found in FirebaseArray %@", key, self.snapshots]; - @throw [NSException exceptionWithName:@"FirebaseArrayKeyNotFoundException" - reason:errorReason - userInfo:@{ - @"Key" : key, - @"Array" : self.snapshots - }]; + return NSNotFound; } -#pragma mark - -#pragma mark Public API methods +#pragma mark - Public API methods + +- (NSArray *)items { + return [self.snapshots copy]; +} - (NSUInteger)count { return [self.snapshots count]; @@ -140,8 +169,8 @@ - (FIRDatabaseReference *)refForIndex:(NSUInteger)index { return [(FIRDataSnapshot *)[self.snapshots objectAtIndex:index] ref]; } -- (id)objectAtIndexedSubscript:(NSUInteger)index{ - return [self objectAtIndex:index]; +- (id)objectAtIndexedSubscript:(NSUInteger)index { + return [self objectAtIndex:index]; } - (void)setObject:(id)obj atIndexedSubscript:(NSUInteger)index{ diff --git a/FirebaseUI/Database/Implementation/FirebaseCollectionViewDataSource.m b/FirebaseUI/Database/Implementation/FirebaseCollectionViewDataSource.m index 3b64ab41de9..8c10cebc6b9 100644 --- a/FirebaseUI/Database/Implementation/FirebaseCollectionViewDataSource.m +++ b/FirebaseUI/Database/Implementation/FirebaseCollectionViewDataSource.m @@ -22,10 +22,14 @@ @import FirebaseDatabase; +@interface FirebaseCollectionViewDataSource () +@property(strong, nonatomic, readwrite) void (^populateCell) +(__kindof UICollectionViewCell *cell, __kindof NSObject *object); +@end + @implementation FirebaseCollectionViewDataSource -#pragma mark - -#pragma mark FirebaseDataSource initializer methods +#pragma mark - FirebaseDataSource initializer methods - (instancetype)initWithRef:(FIRDatabaseReference *)ref cellReuseIdentifier:(NSString *)identifier @@ -84,9 +88,9 @@ - (instancetype)initWithRef:(FIRDatabaseReference *)ref prototypeReuseIdentifier:(NSString *)identifier view:(UICollectionView *)collectionView { return [self initWithQuery:ref - modelClass:model - prototypeReuseIdentifier:identifier - view:collectionView]; + modelClass:model + prototypeReuseIdentifier:identifier + view:collectionView]; } - (instancetype)initWithRef:(FIRDatabaseReference *)ref @@ -199,8 +203,7 @@ - (instancetype)initWithQuery:(FIRDatabaseQuery *)query self.modelClass = model; self.cellClass = cell; self.reuseIdentifier = identifier; - self.populateCell = ^(id cell, id object) { - }; + self.populateCell = ^(id cell, id object) {}; if (!self.hasPrototypeCell) { [self.collectionView registerClass:self.cellClass @@ -225,8 +228,7 @@ - (instancetype)initWithQuery:(FIRDatabaseQuery *)query self.collectionView = collectionView; self.modelClass = model; self.reuseIdentifier = identifier; - self.populateCell = ^(id cell, id object) { - }; + self.populateCell = ^(id cell, id object) {}; UINib *nib = [UINib nibWithNibName:nibName bundle:nil]; [self.collectionView registerNib:nib forCellWithReuseIdentifier:self.reuseIdentifier]; @@ -234,38 +236,37 @@ - (instancetype)initWithQuery:(FIRDatabaseQuery *)query return self; } -#pragma mark - -#pragma mark FirebaseCollectionDelegate methods +#pragma mark - FirebaseArrayDelegate methods -- (void)childAdded:(id)obj atIndex:(NSUInteger)index { +- (void)array:(FirebaseArray *)array didAddObject:(id)object atIndex:(NSUInteger)index { [self.collectionView insertItemsAtIndexPaths:@[ [NSIndexPath indexPathForItem:index inSection:0] ]]; } -- (void)childChanged:(id)obj atIndex:(NSUInteger)index { +- (void)array:(FirebaseArray *)array didChangeObject:(id)object atIndex:(NSUInteger)index { [self.collectionView - reloadItemsAtIndexPaths:@[ [NSIndexPath indexPathForRow:index inSection:0] ]]; + reloadItemsAtIndexPaths:@[ [NSIndexPath indexPathForItem:index inSection:0] ]]; } -- (void)childRemoved:(id)obj atIndex:(NSUInteger)index { +- (void)array:(FirebaseArray *)array didRemoveObject:(id)object atIndex:(NSUInteger)index { [self.collectionView - deleteItemsAtIndexPaths:@[ [NSIndexPath indexPathForRow:index inSection:0] ]]; + deleteItemsAtIndexPaths:@[ [NSIndexPath indexPathForItem:index inSection:0] ]]; } -- (void)childMoved:(id)obj fromIndex:(NSUInteger)fromIndex toIndex:(NSUInteger)toIndex { - [self.collectionView moveItemAtIndexPath:[NSIndexPath indexPathForRow:fromIndex inSection:0] - toIndexPath:[NSIndexPath indexPathForRow:toIndex inSection:0]]; +- (void)array:(FirebaseArray *)array didMoveObject:(id)object + fromIndex:(NSUInteger)fromIndex toIndex:(NSUInteger)toIndex { + [self.collectionView moveItemAtIndexPath:[NSIndexPath indexPathForItem:fromIndex inSection:0] + toIndexPath:[NSIndexPath indexPathForItem:toIndex inSection:0]]; } -#pragma mark - -#pragma mark UICollectionViewDataSource methods +#pragma mark - UICollectionViewDataSource methods - (nonnull UICollectionViewCell *)collectionView:(nonnull UICollectionView *)collectionView cellForItemAtIndexPath:(nonnull NSIndexPath *)indexPath { id cell = [self.collectionView dequeueReusableCellWithReuseIdentifier:self.reuseIdentifier forIndexPath:indexPath]; - FIRDataSnapshot *snap = [self.array objectAtIndex:indexPath.row]; + FIRDataSnapshot *snap = [self.items objectAtIndex:indexPath.row]; if (![self.modelClass isSubclassOfClass:[FIRDataSnapshot class]]) { id model = [[self.modelClass alloc] init]; // TODO: replace setValuesForKeysWithDictionary with client API @@ -285,13 +286,30 @@ - (NSInteger)numberOfSectionsInCollectionView:(nonnull UICollectionView *)collec - (NSInteger)collectionView:(nonnull UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { - return [self.array count]; + return self.count; } -- (void)populateCellWithBlock: - (void (^)(__kindof UICollectionViewCell *cell, - __kindof NSObject *object))callback { +- (void)populateCellWithBlock:(void (^)(__kindof UICollectionViewCell *cell, + __kindof NSObject *object))callback { self.populateCell = callback; } +#pragma mark - Accessors + +- (void)setModelClass:(Class)modelClass { + if (modelClass == nil) { + _modelClass = [FIRDataSnapshot class]; + } else { + _modelClass = modelClass; + } +} + +- (void)setCellClass:(Class)cellClass { + if (cellClass == nil) { + _cellClass = [UICollectionViewCell class]; + } else { + _cellClass = cellClass; + } +} + @end diff --git a/FirebaseUI/Database/Implementation/FirebaseDataSource.m b/FirebaseUI/Database/Implementation/FirebaseDataSource.m index cc3547ee786..6feb466041e 100644 --- a/FirebaseUI/Database/Implementation/FirebaseDataSource.m +++ b/FirebaseUI/Database/Implementation/FirebaseDataSource.m @@ -21,28 +21,37 @@ #import "FirebaseDataSource.h" @interface FirebaseDataSource () -@property(copy, nonatomic) void (^cancelBlock)(NSError *); + +@property(copy, nonatomic, nonnull) void (^cancelBlock)(NSError *); + +/** + * The FirebaseArray which backs the instance of the datasource. + */ +@property(strong, nonatomic, nonnull) FirebaseArray *array; + @end @implementation FirebaseDataSource -#pragma mark - -#pragma mark Initializer methods +#pragma mark - Initializer methods - (instancetype)initWithArray:(FirebaseArray *)array { self = [super init]; if (self) { - self.array = array; - self.array.delegate = self; + _array = array; + _array.delegate = self; } return self; } -#pragma mark - -#pragma mark API methods +#pragma mark - API methods + +- (NSArray *)items { + return [self.array.items copy]; +} - (NSUInteger)count { - return [self.array count]; + return self.array.count; } - (id)objectAtIndex:(NSUInteger)index { @@ -57,8 +66,7 @@ - (void)cancelWithBlock:(void (^)(NSError *))block { self.cancelBlock = block; } -#pragma mark - -#pragma mark FirebaseArrayDelegate methods +#pragma mark - FirebaseArrayDelegate methods - (void)canceledWithError:(NSError *)error { if (self.cancelBlock != nil) { diff --git a/FirebaseUI/Database/Implementation/FirebaseTableViewDataSource.m b/FirebaseUI/Database/Implementation/FirebaseTableViewDataSource.m index 2dd65cd4c96..73d9ab19544 100644 --- a/FirebaseUI/Database/Implementation/FirebaseTableViewDataSource.m +++ b/FirebaseUI/Database/Implementation/FirebaseTableViewDataSource.m @@ -24,8 +24,7 @@ @implementation FirebaseTableViewDataSource -#pragma mark - -#pragma mark FirebaseDataSource initializer methods +#pragma mark - FirebaseDataSource initializer methods - (instancetype)initWithRef:(FIRDatabaseReference *)ref cellReuseIdentifier:(NSString *)identifier @@ -236,45 +235,44 @@ - (instancetype)initWithQuery:(FIRDatabaseQuery *)query return self; } -#pragma mark - -#pragma mark FirebaseCollectionDelegate methods +#pragma mark - FirebaseArrayDelegate methods -- (void)childAdded:(id)obj atIndex:(NSUInteger)index { +- (void)array:(FirebaseArray *)array didAddObject:(id)object atIndex:(NSUInteger)index { [self.tableView beginUpdates]; [self.tableView insertRowsAtIndexPaths:@[ [NSIndexPath indexPathForRow:index inSection:0] ] withRowAnimation:UITableViewRowAnimationAutomatic]; [self.tableView endUpdates]; } -- (void)childChanged:(id)obj atIndex:(NSUInteger)index { +- (void)array:(FirebaseArray *)array didChangeObject:(id)object atIndex:(NSUInteger)index { [self.tableView beginUpdates]; [self.tableView reloadRowsAtIndexPaths:@[ [NSIndexPath indexPathForRow:index inSection:0] ] withRowAnimation:UITableViewRowAnimationAutomatic]; [self.tableView endUpdates]; } -- (void)childRemoved:(id)obj atIndex:(NSUInteger)index { +- (void)array:(FirebaseArray *)array didRemoveObject:(id)object atIndex:(NSUInteger)index { [self.tableView beginUpdates]; [self.tableView deleteRowsAtIndexPaths:@[ [NSIndexPath indexPathForRow:index inSection:0] ] withRowAnimation:UITableViewRowAnimationAutomatic]; [self.tableView endUpdates]; } -- (void)childMoved:(id)obj fromIndex:(NSUInteger)fromIndex toIndex:(NSUInteger)toIndex { +- (void)array:(FirebaseArray *)array didMoveObject:(id)object + fromIndex:(NSUInteger)fromIndex toIndex:(NSUInteger)toIndex { [self.tableView beginUpdates]; [self.tableView moveRowAtIndexPath:[NSIndexPath indexPathForRow:fromIndex inSection:0] toIndexPath:[NSIndexPath indexPathForRow:toIndex inSection:0]]; [self.tableView endUpdates]; } -#pragma mark - -#pragma mark UITableViewDataSource methods +#pragma mark - UITableViewDataSource methods - (id)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { id cell = [self.tableView dequeueReusableCellWithIdentifier:self.reuseIdentifier forIndexPath:indexPath]; - FIRDataSnapshot *snap = [self.array objectAtIndex:indexPath.row]; + FIRDataSnapshot *snap = [self.items objectAtIndex:indexPath.row]; if (![self.modelClass isSubclassOfClass:[FIRDataSnapshot class]]) { id model = [[self.modelClass alloc] init]; // TODO: replace setValuesForKeysWithDictionary with client API @@ -289,12 +287,22 @@ - (id)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)in } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { - return [self.array count]; + return self.count; } - (void)populateCellWithBlock:(void (^)(__kindof UITableViewCell *cell, - __kindof NSObject *object))callback { + __kindof NSObject *object))callback { self.populateCell = callback; } +#pragma mark - Accessors + +- (void)setModelClass:(Class)modelClass { + if (modelClass == nil) { + _modelClass = [FIRDataSnapshot class]; + } else { + _modelClass = modelClass; + } +} + @end diff --git a/FirebaseUITests/FirebaseArrayTest.m b/FirebaseUITests/FirebaseArrayTest.m new file mode 100644 index 00000000000..191d82c9f1d --- /dev/null +++ b/FirebaseUITests/FirebaseArrayTest.m @@ -0,0 +1,381 @@ +// clang-format off + +// +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// clang-format on + +@import XCTest; + +#import "FirebaseArray.h" +#import "FirebaseArrayTestUtils.h" + +@interface FirebaseArrayTest : XCTestCase + +@property (nonatomic, nullable) FUIFirebaseArrayTestDelegate *arrayDelegate; +@property (nonatomic, nullable) FUITestObservable *observable; +@property (nonatomic, nullable) FirebaseArray *firebaseArray; +@property (nonatomic, nullable) FUIFakeSnapshot *snap; + +@end + +@implementation FirebaseArrayTest + +- (void)setUp { + [super setUp]; + self.arrayDelegate = [[FUIFirebaseArrayTestDelegate alloc] init]; + self.snap = [[FUIFakeSnapshot alloc] init]; + self.observable = [[FUITestObservable alloc] init]; + self.firebaseArray = [[FirebaseArray alloc] initWithQuery:self.observable]; + self.firebaseArray.delegate = self.arrayDelegate; +} + +- (void)tearDown { + [super tearDown]; + [self.observable removeAllObservers]; + self.arrayDelegate = nil; +} + +#pragma mark - Insertion + +- (void)testFirebaseArrayCanBeInitialized { + XCTAssertNotNil(self.firebaseArray, @"expected FirebaseArray to not be nil when initialized"); +} + +- (void)testEmptyFirebaseArrayUpdatesCountOnInsert { + // Test delegate + __block BOOL delegateWasCalled = NO; + __block BOOL expectedParametersWereCorrect = NO; + self.arrayDelegate.didAddObject = ^(FirebaseArray *array, id object, NSUInteger index) { + // Xcode complains about retain cycles if an XCTAssert is placed in here. + delegateWasCalled = YES; + expectedParametersWereCorrect = (array == self.firebaseArray && + object == self.snap && + index == 0); + }; + + // Test insert + self.snap.key = @"snapshot"; + [self.observable sendEvent:FIRDataEventTypeChildAdded + withObject:self.snap + previousKey:nil + error:nil]; + // Array expectations + XCTAssert(self.firebaseArray.count == 1, @"expected empty array to contain one item after insert"); + + // Delegate expectations + XCTAssert(delegateWasCalled, @"expected delegate to receive callback for insertion"); + XCTAssert(expectedParametersWereCorrect, @"unexpected parameter in delegate callback"); +} + +- (void)testFirebaseArrayCanInsertInMiddle { + // Setup boilerplate + [self.observable populateWithCount:10]; + self.snap.key = @"5"; + + // Test delegate + __block BOOL delegateWasCalled = NO; + __block BOOL expectedParametersWereCorrect = NO; + self.arrayDelegate.didAddObject = ^(FirebaseArray *array, id object, NSUInteger index) { + // Xcode complains about retain cycles if an XCTAssert is placed in here. + delegateWasCalled = YES; + expectedParametersWereCorrect = (array == self.firebaseArray && + object == self.snap && + index == 5); + }; + + // Insert in middle + [self.observable sendEvent:FIRDataEventTypeChildAdded withObject:self.snap previousKey:@"4" error:nil]; + + // Array expectations + NSArray *items = self.firebaseArray.items; + NSArray *expected = @[@"0", @"1", @"2", @"3", @"4", @"5", @"5", @"6", @"7", @"8", @"9"]; + NSMutableArray *result = [NSMutableArray array]; + for (FUIFakeSnapshot *snapshot in items) { + [result addObject:snapshot.key]; + } + XCTAssert([result isEqual:expected], @"expected firebaseArray contents to equal %@, got %@", expected, [result copy]); + + // Delegate expectations + XCTAssert(delegateWasCalled, @"expected delegate to receive callback for insertion"); + XCTAssert(expectedParametersWereCorrect, @"unexpected parameter in delegate callback"); +} + +- (void)testFirebaseArrayCanInsertAtBeginning { + // Setup boilerplate + [self.observable populateWithCount:10]; + self.snap.key = @"0"; + + // Test delegate + __block BOOL delegateWasCalled = NO; + __block BOOL expectedParametersWereCorrect = NO; + self.arrayDelegate.didAddObject = ^(FirebaseArray *array, id object, NSUInteger index) { + // Xcode complains about retain cycles if an XCTAssert is placed in here. + delegateWasCalled = YES; + expectedParametersWereCorrect = (array == self.firebaseArray && + object == self.snap && + index == 0); + }; + + // Insert at beginning + [self.observable sendEvent:FIRDataEventTypeChildAdded withObject:self.snap previousKey:nil error:nil]; + + // Array expectations + NSArray *items = self.firebaseArray.items; + NSArray *expected = @[@"0", @"0", @"1", @"2", @"3", @"4", @"5", @"6", @"7", @"8", @"9"]; + NSMutableArray *result = [NSMutableArray array]; + for (FUIFakeSnapshot *snapshot in items) { + [result addObject:snapshot.key]; + } + XCTAssert([result isEqual:expected], @"expected firebaseArray contents to equal %@, got %@", expected, [result copy]); + + // Delegate expectations + XCTAssert(delegateWasCalled, @"expected delegate to receive callback for insertion"); + XCTAssert(expectedParametersWereCorrect, @"unexpected parameter in delegate callback"); +} + +- (void)testFirebaseArrayCanInsertAtEnd { + // Setup boilerplate + [self.observable populateWithCount:10]; + self.snap.key = @"10"; + + // Test delegate + __block BOOL delegateWasCalled = NO; + __block BOOL expectedParametersWereCorrect = NO; + self.arrayDelegate.didAddObject = ^(FirebaseArray *array, id object, NSUInteger index) { + // Xcode complains about retain cycles if an XCTAssert is placed in here. + delegateWasCalled = YES; + expectedParametersWereCorrect = (array == self.firebaseArray && + object == self.snap && + index == 10); + }; + + // Insert at end + [self.observable sendEvent:FIRDataEventTypeChildAdded withObject:self.snap previousKey:@"9" error:nil]; + + // Array expectations + NSArray *items = self.firebaseArray.items; + NSArray *expected = @[@"0", @"1", @"2", @"3", @"4", @"5", @"6", @"7", @"8", @"9", @"10"]; + NSMutableArray *result = [NSMutableArray array]; + for (FUIFakeSnapshot *snapshot in items) { + [result addObject:snapshot.key]; + } + XCTAssert([result isEqual:expected], @"expected firebaseArray contents to equal %@, got %@", expected, [result copy]); + + // Delegate expectations + XCTAssert(delegateWasCalled, @"expected delegate to receive callback for insertion"); + XCTAssert(expectedParametersWereCorrect, @"unexpected parameter in delegate callback"); +} + +#pragma mark - Deletion + +- (void)testFirebaseArrayCanDeleteOneElementArray { + // Insert a key + self.snap.key = @"snapshot"; + [self.observable sendEvent:FIRDataEventTypeChildAdded + withObject:self.snap + previousKey:nil + error:nil]; + + // Test delegate + __block BOOL delegateWasCalled = NO; + __block BOOL expectedParametersWereCorrect = NO; + self.arrayDelegate.didRemoveObject = ^(FirebaseArray *array, id object, NSUInteger index) { + // Xcode complains about retain cycles if an XCTAssert is placed in here. + delegateWasCalled = YES; + expectedParametersWereCorrect = (array == self.firebaseArray && + object == self.snap && + index == 0); + }; + + // Delete + [self.observable sendEvent:FIRDataEventTypeChildRemoved + withObject:self.snap + previousKey:nil + error:nil]; + // Array expectation + XCTAssert(self.firebaseArray.count == 0, + @"expected empty array to still be empty after one insertion and one deletion"); + + // Delegate expectations + XCTAssert(delegateWasCalled, @"expected delegate to receive callback for deletion"); + XCTAssert(expectedParametersWereCorrect, @"unexpected parameter in delegate callback"); +} + +- (void)testFirebaseArrayCanDeleteFirstElement { + [self.observable populateWithCount:10]; + self.snap.key = @"0"; + + // Test delegate + __block BOOL delegateWasCalled = NO; + __block BOOL expectedParametersWereCorrect = NO; + self.arrayDelegate.didRemoveObject = ^(FirebaseArray *array, id object, NSUInteger index) { + // Xcode complains about retain cycles if an XCTAssert is placed in here. + delegateWasCalled = YES; + expectedParametersWereCorrect = (array == self.firebaseArray && + object == self.snap && + index == 0); + }; + + [self.observable sendEvent:FIRDataEventTypeChildRemoved withObject:self.snap previousKey:nil error:nil]; + + // Array expectations + NSArray *items = self.firebaseArray.items; + NSArray *expected = @[@"1", @"2", @"3", @"4", @"5", @"6", @"7", @"8", @"9"]; + NSMutableArray *result = [NSMutableArray array]; + for (FUIFakeSnapshot *snapshot in items) { + [result addObject:snapshot.key]; + } + XCTAssert([result isEqual:expected], @"expected firebaseArray contents to equal %@, got %@", expected, [result copy]); + + // Delegate expectations + XCTAssert(delegateWasCalled, @"expected delegate to receive callback for deletion"); + XCTAssert(expectedParametersWereCorrect, @"unexpected parameter in delegate callback"); +} + +- (void)testFirebaseArrayCanDeleteLastElement { + [self.observable populateWithCount:10]; + self.snap.key = @"9"; + + // Test delegate + __block BOOL delegateWasCalled = NO; + __block BOOL expectedParametersWereCorrect = NO; + self.arrayDelegate.didRemoveObject = ^(FirebaseArray *array, id object, NSUInteger index) { + // Xcode complains about retain cycles if an XCTAssert is placed in here. + delegateWasCalled = YES; + expectedParametersWereCorrect = (array == self.firebaseArray && + object == self.snap && + index == 9); + }; + + // Delete last element + [self.observable sendEvent:FIRDataEventTypeChildRemoved withObject:self.snap previousKey:@"8" error:nil]; + + // Array expectations + NSArray *items = self.firebaseArray.items; + NSArray *expected = @[@"0", @"1", @"2", @"3", @"4", @"5", @"6", @"7", @"8"]; + NSMutableArray *result = [NSMutableArray array]; + for (FUIFakeSnapshot *snapshot in items) { + [result addObject:snapshot.key]; + } + XCTAssert([result isEqual:expected], @"expected firebaseArray contents to equal %@, got %@", expected, [result copy]); + + // Delegate expectations + XCTAssert(delegateWasCalled, @"expected delegate to receive callback for deletion"); + XCTAssert(expectedParametersWereCorrect, @"unexpected parameter in delegate callback"); +} + +- (void)testFirebaseArrayCanDeleteMiddleElement { + [self.observable populateWithCount:10]; + self.snap.key = @"5"; + + // Test delegate + __block BOOL delegateWasCalled = NO; + __block BOOL expectedParametersWereCorrect = NO; + self.arrayDelegate.didRemoveObject = ^(FirebaseArray *array, id object, NSUInteger index) { + // Xcode complains about retain cycles if an XCTAssert is placed in here. + delegateWasCalled = YES; + expectedParametersWereCorrect = (array == self.firebaseArray && + object == self.snap && + index == 5); + }; + + // Delete element + [self.observable sendEvent:FIRDataEventTypeChildRemoved withObject:self.snap previousKey:@"4" error:nil]; + + // Array expectation + NSArray *items = self.firebaseArray.items; + NSArray *expected = @[@"0", @"1", @"2", @"3", @"4", @"6", @"7", @"8", @"9"]; + NSMutableArray *result = [NSMutableArray array]; + for (FUIFakeSnapshot *snapshot in items) { + [result addObject:snapshot.key]; + } + XCTAssert([result isEqual:expected], @"expected firebaseArray contents to equal %@, got %@", expected, [result copy]); + + // Delegate expectations + XCTAssert(delegateWasCalled, @"expected delegate to receive callback for deletion"); + XCTAssert(expectedParametersWereCorrect, @"unexpected parameter in delegate callback"); +} + +#pragma mark - Modifying elements + +- (void)testFirebaseArrayCanModifyElement { + [self.observable populateWithCount:10]; + self.snap.key = @"5"; + self.snap.value = @"a value"; + + // Test delegate + __block BOOL delegateWasCalled = NO; + __block BOOL expectedParametersWereCorrect = NO; + self.arrayDelegate.didChangeObject = ^(FirebaseArray *array, id object, NSUInteger index) { + // Xcode complains about retain cycles if an XCTAssert is placed in here. + delegateWasCalled = YES; + expectedParametersWereCorrect = (array == self.firebaseArray && + object == self.snap && + index == 5); + }; + + [self.observable sendEvent:FIRDataEventTypeChildChanged withObject:self.snap previousKey:@"4" error:nil]; + + // Array expectation + NSArray *items = self.firebaseArray.items; + NSArray *expected = @[@"0", @"1", @"2", @"3", @"4", @"a value", @"6", @"7", @"8", @"9"]; + NSMutableArray *result = [NSMutableArray array]; + for (FUIFakeSnapshot *snapshot in items) { + [result addObject:snapshot.value]; + } + XCTAssert([result isEqual:expected], @"expected firebaseArray contents to equal %@, got %@", expected, [result copy]); + + // Delegate expectations + XCTAssert(delegateWasCalled, @"expected delegate to receive callback for deletion"); + XCTAssert(expectedParametersWereCorrect, @"unexpected parameter in delegate callback"); +} + +#pragma mark - Moving elements + +- (void)testFirebaseArrayCanMoveElement { + [self.observable populateWithCount:10]; + self.snap.key = @"8"; + + // Test delegate + __block BOOL delegateWasCalled = NO; + __block BOOL expectedParametersWereCorrect = NO; + self.arrayDelegate.didMoveObject = ^(FirebaseArray *array, id object, NSUInteger from, NSUInteger to) { + // Xcode complains about retain cycles if an XCTAssert is placed in here. + delegateWasCalled = YES; + expectedParametersWereCorrect = (array == self.firebaseArray && + object == self.snap && + from == 8 && to == 3); + }; + + // Move 8 to after 2 + [self.observable sendEvent:FIRDataEventTypeChildMoved withObject:self.snap previousKey:@"2" error:nil]; + + // Array expectation + NSArray *items = self.firebaseArray.items; + NSArray *expected = @[@"0", @"1", @"2", @"8", @"3", @"4", @"5", @"6", @"7", @"9"]; + NSMutableArray *result = [NSMutableArray array]; + for (FUIFakeSnapshot *snapshot in items) { + [result addObject:snapshot.key]; + } + XCTAssert([result isEqual:expected], @"expected firebaseArray contents to equal %@, got %@", expected, [result copy]); + + // Delegate expectations + XCTAssert(delegateWasCalled, @"expected delegate to receive callback for deletion"); + XCTAssert(expectedParametersWereCorrect, @"unexpected parameter in delegate callback"); +} + +@end diff --git a/FirebaseUITests/FirebaseArrayTestUtils.h b/FirebaseUITests/FirebaseArrayTestUtils.h new file mode 100644 index 00000000000..f746a0380eb --- /dev/null +++ b/FirebaseUITests/FirebaseArrayTestUtils.h @@ -0,0 +1,76 @@ +// clang-format off + +// +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// clang-format on + +#import "FirebaseArray.h" + +NS_ASSUME_NONNULL_BEGIN + +// Dumb object holding a pair of blocks and a data event type. +@interface FUIDataEventHandler: NSObject +@property (nonatomic, assign) FIRDataEventType event; +@property (nonatomic, copy) void (^success)(FIRDataSnapshot *_Nonnull, NSString *_Nullable); +@property (nonatomic, copy) void (^cancelled)(NSError *_Nonnull); +@end + +// Horrible abuse of ObjC type system, since FirebaseArray is unfortunately coupled to +// FIRDataSnapshot +@interface FUIFakeSnapshot: NSObject +- (instancetype)initWithKey:(NSString *)key value:(NSString *)value; +@property (nonatomic, copy) NSString *key; +@property (nonatomic, copy) NSString *value; +@end + +// A dummy observable so we can test this without relying on an internet connection. +@interface FUITestObservable: NSObject + +// Map of handles to observers. +@property (nonatomic, readonly) NSMutableDictionary *observers; + +// Incremented to generate unique handles. +@property (nonatomic, readonly, assign) FIRDatabaseHandle current; + +- (void)removeAllObservers; + +// Sends an event to the observable's observers. +- (void)sendEvent:(FIRDataEventType)event + withObject:(nullable FUIFakeSnapshot *)object + previousKey:(nullable NSString *)string + error:(nullable NSError *)error; + +// Inserts sequentially with data provided by the `generator` block. Snapshot keys +// are increasing integers as strings, snapshot values are strings returned by the +// `generator` block. +- (void)populateWithCount:(NSUInteger)count generator:(NSString *(^)(NSUInteger))generator; + +// Sends a bunch of insertion events with snapshot keys as integer strings (i.e. @"0") of increasing +// order, starting from 0. +- (void)populateWithCount:(NSUInteger)count; + +@end + +@interface FUIFirebaseArrayTestDelegate : NSObject +@property (nonatomic, copy) void (^queryCancelled)(FirebaseArray *array, NSError *error); +@property (nonatomic, copy) void (^didAddObject)(FirebaseArray *array, id object, NSUInteger index); +@property (nonatomic, copy) void (^didChangeObject)(FirebaseArray *array, id object, NSUInteger index); +@property (nonatomic, copy) void (^didRemoveObject)(FirebaseArray *array, id object, NSUInteger index); +@property (nonatomic, copy) void (^didMoveObject)(FirebaseArray *array, id object, NSUInteger fromIndex, NSUInteger toIndex); +@end + +NS_ASSUME_NONNULL_END diff --git a/FirebaseUITests/FirebaseArrayTestUtils.m b/FirebaseUITests/FirebaseArrayTestUtils.m new file mode 100644 index 00000000000..3de71c37b31 --- /dev/null +++ b/FirebaseUITests/FirebaseArrayTestUtils.m @@ -0,0 +1,135 @@ +// clang-format off + +// +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// clang-format on + +#import "FirebaseArrayTestUtils.h" + +@implementation FUIDataEventHandler +@end + +@implementation FUIFakeSnapshot +- (instancetype)initWithKey:(NSString *)key value:(NSString *)value { + self = [super init]; + if (self != nil) { + _key = [key copy]; + _value = [value copy]; + } + return self; +} +@end + +@implementation FUITestObservable + +- (instancetype)init { + self = [super init]; + if (self != nil) { + _observers = [NSMutableDictionary dictionary]; + } + return self; +} + +- (void)removeObserverWithHandle:(FIRDatabaseHandle)handle { + [self.observers removeObjectForKey:@(handle)]; +} + +- (void)removeAllObservers { + _observers = [NSMutableDictionary dictionary]; +} + +- (FIRDatabaseHandle)observeEventType:(FIRDataEventType)eventType + andPreviousSiblingKeyWithBlock:(void (^)(FIRDataSnapshot * _Nonnull, NSString * _Nullable))block + withCancelBlock:(void (^)(NSError * _Nonnull))cancelBlock { + FUIDataEventHandler *handler = [[FUIDataEventHandler alloc] init]; + handler.event = eventType; + handler.success = block; + handler.cancelled = cancelBlock; + + NSNumber *key = @(self.current); + _current++; + self.observers[key] = handler; + return key.unsignedIntegerValue; +} + +- (void)sendEvent:(FIRDataEventType)event + withObject:(FUIFakeSnapshot *)object + previousKey:(NSString *)string + error:(NSError *)error { + NSArray *allKeys = self.observers.allKeys; + for (NSNumber *key in allKeys) { + FUIDataEventHandler *handler = self.observers[key]; + if (handler.event == event) { + if (error != nil) { handler.cancelled(error); } + else { handler.success((FIRDataSnapshot *)object, string); } + } + } +} + +- (void)populateWithCount:(NSUInteger)count generator:(NSString *(^)(NSUInteger))generator { + NSString *previous = nil; + for (NSUInteger i = 0; i < count; i++) { + FUIFakeSnapshot *snap = [[FUIFakeSnapshot alloc] init]; + snap.value = generator(i); + snap.key = @(i).stringValue; + [self sendEvent:FIRDataEventTypeChildAdded withObject:snap previousKey:previous error:nil]; + previous = snap.key; + } +} + +- (void)populateWithCount:(NSUInteger)count { + [self populateWithCount:count generator:^NSString *(NSUInteger index) { + return @(index).stringValue; + }]; +} + +@end + +@implementation FUIFirebaseArrayTestDelegate + +- (void)array:(FirebaseArray *)array didAddObject:(id)object atIndex:(NSUInteger)index { + if (self.didAddObject != NULL) { + self.didAddObject(array, object, index); + } +} + +- (void)array:(FirebaseArray *)array didChangeObject:(id)object atIndex:(NSUInteger)index { + if (self.didChangeObject != NULL) { + self.didChangeObject(array, object, index); + } +} + +- (void)array:(FirebaseArray *)array didRemoveObject:(id)object atIndex:(NSUInteger)index { + if (self.didRemoveObject != NULL) { + self.didRemoveObject(array, object, index); + } +} + +- (void)array:(FirebaseArray *)array didMoveObject:(id)object + fromIndex:(NSUInteger)fromIndex toIndex:(NSUInteger)toIndex { + if (self.didMoveObject != NULL) { + self.didMoveObject(array, object, fromIndex, toIndex); + } +} + +- (void)array:(FirebaseArray *)array queryCancelledWithError:(NSError *)error { + if (self.queryCancelled != NULL) { + self.queryCancelled(array, error); + } +} + +@end diff --git a/FirebaseUITests/FirebaseCollectionViewDataSourceTest.m b/FirebaseUITests/FirebaseCollectionViewDataSourceTest.m new file mode 100644 index 00000000000..0763f410716 --- /dev/null +++ b/FirebaseUITests/FirebaseCollectionViewDataSourceTest.m @@ -0,0 +1,115 @@ +// clang-format off + +// +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// clang-format on + +@import XCTest; + +#import "FirebaseCollectionViewDataSource.h" +#import "FirebaseArrayTestUtils.h" + +static NSString *const kTestReuseIdentifier = @"FirebaseCollectionViewDataSourceTest"; + +@interface FirebaseCollectionViewDataSourceTest : XCTestCase +@property (nonatomic) UICollectionView *collectionView; +@property (nonatomic) FUITestObservable *observable; +@property (nonatomic) FirebaseCollectionViewDataSource *dataSource; +@end + +@implementation FirebaseCollectionViewDataSourceTest + +- (void)setUp { + [super setUp]; + [UIView setAnimationsEnabled:NO]; + self.collectionView = [[UICollectionView alloc] initWithFrame:CGRectMake(0, 0, 100, 100) + collectionViewLayout:[[UICollectionViewFlowLayout alloc] init]]; + [self.collectionView registerClass:[UICollectionViewCell class] + forCellWithReuseIdentifier:kTestReuseIdentifier]; + + self.observable = [[FUITestObservable alloc] init]; + // Horrible abuse of type system, knowing that the initializer passes the observable straight to + // FirebaseArray anyway. + self.dataSource = [[FirebaseCollectionViewDataSource alloc] initWithRef:(FIRDatabaseReference *)self.observable + cellReuseIdentifier:kTestReuseIdentifier + view:self.collectionView]; + [self.dataSource populateCellWithBlock:^(__kindof UICollectionViewCell *_Nonnull cell, + FUIFakeSnapshot * _Nonnull object) { + cell.accessibilityValue = object.key; + }]; + self.collectionView.dataSource = self.dataSource; + + // Removing this NSLog causes the tests to crash since `numberOfItemsInSection` + // actually pulls updates from the data source or something + NSLog(@"count: %lu", [self.collectionView numberOfItemsInSection:0]); + + [self.observable populateWithCount:10]; +} + +- (void)tearDown { + [super tearDown]; + [self.observable removeAllObservers]; + [UIView setAnimationsEnabled:YES]; +} + +- (void)testItHasACount { + NSUInteger count = self.dataSource.count; + XCTAssert(count == 10, @"expected data source to have 10 elements after 10 insertions, but got %lu", count); +} + +- (void)testItPopulatesCells { + UICollectionViewCell *cell = [self.dataSource collectionView:self.collectionView + cellForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]; + XCTAssert([cell.accessibilityValue isEqualToString:@"0"], + @"expected cellForItemAtIndexPath to populate accessibility label with value '0', \ + but instead got %@", cell.accessibilityValue); + + cell = [self.dataSource collectionView:self.collectionView + cellForItemAtIndexPath:[NSIndexPath indexPathForItem:5 inSection:0]]; + XCTAssert([cell.accessibilityValue isEqualToString:@"5"], + @"expected cellForItemAtIndexPath to populate accessibility label with value '5', \ + but instead got %@", cell.accessibilityValue); + + cell = [self.dataSource collectionView:self.collectionView + cellForItemAtIndexPath:[NSIndexPath indexPathForItem:9 inSection:0]]; + XCTAssert([cell.accessibilityValue isEqualToString:@"9"], + @"expected cellForItemAtIndexPath to populate accessibility label with value '9', \ + but instead got %@", cell.accessibilityValue); +} + +- (void)testItDeletesCells { + FUIFakeSnapshot *snap = [[FUIFakeSnapshot alloc] init]; + snap.key = @"0"; + [self.observable sendEvent:FIRDataEventTypeChildRemoved withObject:snap previousKey:nil error:nil]; + UICollectionViewCell *cell = [self.dataSource collectionView:self.collectionView + cellForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]; + XCTAssert([cell.accessibilityValue isEqualToString:@"1"], @"expected element with previous index 1 \ + to be zeroth after deletion of zeroth element, but got %@", cell.accessibilityValue); +} + +- (void)testItMovesCells { + FUIFakeSnapshot *snap = [[FUIFakeSnapshot alloc] init]; + snap.key = @"5"; + [self.observable sendEvent:FIRDataEventTypeChildMoved withObject:snap previousKey:@"3" error:nil]; + UICollectionViewCell *cell = [self.dataSource collectionView:self.collectionView + cellForItemAtIndexPath:[NSIndexPath indexPathForItem:4 inSection:0]]; + + XCTAssert([cell.accessibilityValue isEqualToString:@"5"], @"expected element with index 5 to move to index 4, \ + found %@ at index 4 instead", cell.accessibilityValue); +} + +@end diff --git a/FirebaseUITests/FirebaseTableViewDataSourceTest.m b/FirebaseUITests/FirebaseTableViewDataSourceTest.m new file mode 100644 index 00000000000..aa9e73966b8 --- /dev/null +++ b/FirebaseUITests/FirebaseTableViewDataSourceTest.m @@ -0,0 +1,104 @@ +// clang-format off + +// +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// clang-format on + +@import XCTest; + +#import "FirebaseTableViewDataSource.h" +#import "FirebaseArrayTestUtils.h" + +static NSString *const kTestReuseIdentifier = @"FirebaseTableViewDataSourceTest"; + +@interface FirebaseTableViewDataSourceTest : XCTestCase +@property (nonatomic) UITableView *tableView; +@property (nonatomic) FUITestObservable *observable; +@property (nonatomic) FirebaseTableViewDataSource *dataSource; +@end + +@implementation FirebaseTableViewDataSourceTest + +- (void)setUp { + [super setUp]; + [UIView setAnimationsEnabled:NO]; + self.tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, 100, 100) + style:UITableViewStylePlain]; + [self.tableView registerClass:[UITableViewCell class] + forCellReuseIdentifier:kTestReuseIdentifier]; + + self.observable = [[FUITestObservable alloc] init]; + // Horrible abuse of type system, knowing that the initializer passes the observable straight to + // FirebaseArray anyway. + self.dataSource = [[FirebaseTableViewDataSource alloc] initWithRef:(FIRDatabaseReference *)self.observable + cellReuseIdentifier:kTestReuseIdentifier + view:self.tableView]; + [self.dataSource populateCellWithBlock:^(__kindof UITableViewCell *_Nonnull cell, + FUIFakeSnapshot * _Nonnull object) { + cell.accessibilityValue = object.key; + }]; + self.tableView.dataSource = self.dataSource; + + [self.observable populateWithCount:10]; +} + +- (void)tearDown { + [super tearDown]; + [self.observable removeAllObservers]; + [UIView setAnimationsEnabled:YES]; +} + +- (void)testItHasACount { + NSUInteger count = self.dataSource.count; + XCTAssert(count == 10, @"expected data source to have 10 elements after 10 insertions, but got %lu", count); +} + +- (void)testItPopulatesCells { + UITableViewCell *cell = [self.dataSource tableView:self.tableView + cellForRowAtIndexPath:[NSIndexPath indexPathForRow:5 inSection:0]]; + + XCTAssert([cell.accessibilityValue isEqualToString:@"5"], @"expected cell to have accessibility label \ + equal to its indexpath row (5), but instead got %@", cell.accessibilityValue); +} + +- (void)testItInsertsCells { + FUIFakeSnapshot *snap = [[FUIFakeSnapshot alloc] init]; + snap.key = @"inserted"; + [self.observable sendEvent:FIRDataEventTypeChildAdded withObject:snap previousKey:@"9" error:nil]; + + UITableViewCell *cell = [self.dataSource tableView:self.tableView + cellForRowAtIndexPath:[NSIndexPath indexPathForRow:10 inSection:0]]; + XCTAssert([cell.accessibilityValue isEqualToString:snap.key], @"expected inserted element to be last \ + cell in table view, instead got %@", cell.accessibilityValue); +} + +- (void)testItDeletesCells { + FUIFakeSnapshot *snap = [[FUIFakeSnapshot alloc] init]; + snap.key = @"9"; + [self.observable sendEvent:FIRDataEventTypeChildRemoved withObject:snap previousKey:@"8" error:nil]; + + UITableViewCell *cell = [self.dataSource tableView:self.tableView + cellForRowAtIndexPath:[NSIndexPath indexPathForRow:8 inSection:0]]; + XCTAssert([cell.accessibilityValue isEqualToString:@"8"], @"expected second-to-last element to be last \ + after deletion of last element, instead got %@", cell.accessibilityValue); + XCTAssert(self.dataSource.count == 9, @"expected 10-element data source to have 9 elements after deletion, \ + instead got %lu", self.dataSource.count); +} + +// TODO: add tests for moving and modifying elements + +@end diff --git a/FirebaseUITests/Info.plist b/FirebaseUITests/Info.plist new file mode 100644 index 00000000000..ba72822e872 --- /dev/null +++ b/FirebaseUITests/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + + diff --git a/Podfile b/Podfile index 7c86cd5b376..3d49c9aa9c2 100644 --- a/Podfile +++ b/Podfile @@ -7,6 +7,15 @@ target 'FirebaseUI' do pod 'Firebase/Auth' pod 'FBSDKLoginKit', '~> 4.0' pod 'GoogleSignIn', '~> 4.0' + + target 'FirebaseUITests' do + inherit! :search_paths + pod 'Firebase' + pod 'Firebase/Database' + pod 'Firebase/Auth' + pod 'FBSDKLoginKit', '~> 4.0' + pod 'GoogleSignIn', '~> 4.0' + end end target 'Database' do @@ -34,3 +43,4 @@ target 'Google' do pod 'Firebase/Auth' pod 'GoogleSignIn', '~> 4.0' end +