From be2654c58133181434183d62914136dac74d6efc Mon Sep 17 00:00:00 2001 From: Morgan Chen Date: Mon, 18 Jul 2016 16:52:55 -0700 Subject: [PATCH 01/14] create sample app --- samples/swift/Podfile | 10 + .../swift/uidemo.xcodeproj/project.pbxproj | 575 ++++++++++++++++++ samples/swift/uidemo/AppDelegate.swift | 36 ++ .../AppIcon.appiconset/Contents.json | 73 +++ .../uidemo/Base.lproj/LaunchScreen.storyboard | 27 + .../swift/uidemo/Base.lproj/Main.storyboard | 212 +++++++ .../swift/uidemo/ChatCollectionViewCell.swift | 75 +++ samples/swift/uidemo/ChatViewController.swift | 251 ++++++++ samples/swift/uidemo/Info.plist | 47 ++ samples/swift/uidemo/MenuViewController.swift | 62 ++ samples/swift/uidemo/Sample.swift | 41 ++ samples/swift/uidemo/SampleCell.swift | 48 ++ samples/swift/uidemoTests/Info.plist | 24 + samples/swift/uidemoTests/uidemoTests.swift | 0 14 files changed, 1481 insertions(+) create mode 100644 samples/swift/Podfile create mode 100644 samples/swift/uidemo.xcodeproj/project.pbxproj create mode 100644 samples/swift/uidemo/AppDelegate.swift create mode 100644 samples/swift/uidemo/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 samples/swift/uidemo/Base.lproj/LaunchScreen.storyboard create mode 100644 samples/swift/uidemo/Base.lproj/Main.storyboard create mode 100644 samples/swift/uidemo/ChatCollectionViewCell.swift create mode 100644 samples/swift/uidemo/ChatViewController.swift create mode 100644 samples/swift/uidemo/Info.plist create mode 100644 samples/swift/uidemo/MenuViewController.swift create mode 100644 samples/swift/uidemo/Sample.swift create mode 100644 samples/swift/uidemo/SampleCell.swift create mode 100644 samples/swift/uidemoTests/Info.plist create mode 100644 samples/swift/uidemoTests/uidemoTests.swift diff --git a/samples/swift/Podfile b/samples/swift/Podfile new file mode 100644 index 00000000000..ba3deece0a5 --- /dev/null +++ b/samples/swift/Podfile @@ -0,0 +1,10 @@ +target 'uidemo' do + use_frameworks! + + pod 'FirebaseUI' + + target 'uidemoTests' do + inherit! :search_paths + end + +end diff --git a/samples/swift/uidemo.xcodeproj/project.pbxproj b/samples/swift/uidemo.xcodeproj/project.pbxproj new file mode 100644 index 00000000000..8c79ebc467f --- /dev/null +++ b/samples/swift/uidemo.xcodeproj/project.pbxproj @@ -0,0 +1,575 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 6BEBFBD2CFCDA8A7918028C1 /* Pods_uidemoTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1A128E4DD412913B07E20C37 /* Pods_uidemoTests.framework */; }; + 8DABC9891D3D82D600453807 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8DABC9881D3D82D600453807 /* AppDelegate.swift */; }; + 8DABC98B1D3D82D600453807 /* MenuViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8DABC98A1D3D82D600453807 /* MenuViewController.swift */; }; + 8DABC98E1D3D82D600453807 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8DABC98C1D3D82D600453807 /* Main.storyboard */; }; + 8DABC9901D3D82D600453807 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8DABC98F1D3D82D600453807 /* Assets.xcassets */; }; + 8DABC9931D3D82D600453807 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8DABC9911D3D82D600453807 /* LaunchScreen.storyboard */; }; + 8DABC99E1D3D82D600453807 /* uidemoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8DABC99D1D3D82D600453807 /* uidemoTests.swift */; }; + 8DABC9A91D3D872C00453807 /* Sample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8DABC9A81D3D872C00453807 /* Sample.swift */; }; + 8DABC9AB1D3D947300453807 /* SampleCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8DABC9AA1D3D947300453807 /* SampleCell.swift */; }; + 8DABC9AD1D3D9EAF00453807 /* ChatViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8DABC9AC1D3D9EAF00453807 /* ChatViewController.swift */; }; + 8DABC9AF1D3DA30D00453807 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 8DABC9AE1D3DA30D00453807 /* GoogleService-Info.plist */; }; + 8DDF1AE51D3FF67D001F1160 /* ChatCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8DDF1AE41D3FF67D001F1160 /* ChatCollectionViewCell.swift */; }; + DBA05234601E54F91320B659 /* Pods_uidemo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C9B65C9D0868F18A0BBFF783 /* Pods_uidemo.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 8DABC99A1D3D82D600453807 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 8DABC97D1D3D82D600453807 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 8DABC9841D3D82D600453807; + remoteInfo = uidemo; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 1A128E4DD412913B07E20C37 /* Pods_uidemoTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_uidemoTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 5F8797FA94AF0055875F8117 /* Pods-uidemo.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-uidemo.debug.xcconfig"; path = "Pods/Target Support Files/Pods-uidemo/Pods-uidemo.debug.xcconfig"; sourceTree = ""; }; + 6EC625C321DD275A92C4085C /* Pods-uidemoTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-uidemoTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-uidemoTests/Pods-uidemoTests.release.xcconfig"; sourceTree = ""; }; + 8ADB97851843928A14BC9024 /* Pods-uidemo.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-uidemo.release.xcconfig"; path = "Pods/Target Support Files/Pods-uidemo/Pods-uidemo.release.xcconfig"; sourceTree = ""; }; + 8DABC9851D3D82D600453807 /* uidemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = uidemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 8DABC9881D3D82D600453807 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 8DABC98A1D3D82D600453807 /* MenuViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuViewController.swift; sourceTree = ""; }; + 8DABC98D1D3D82D600453807 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 8DABC98F1D3D82D600453807 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 8DABC9921D3D82D600453807 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 8DABC9941D3D82D600453807 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 8DABC9991D3D82D600453807 /* uidemoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = uidemoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 8DABC99D1D3D82D600453807 /* uidemoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = uidemoTests.swift; sourceTree = ""; }; + 8DABC99F1D3D82D600453807 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 8DABC9A81D3D872C00453807 /* Sample.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Sample.swift; sourceTree = ""; }; + 8DABC9AA1D3D947300453807 /* SampleCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SampleCell.swift; sourceTree = ""; }; + 8DABC9AC1D3D9EAF00453807 /* ChatViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatViewController.swift; sourceTree = ""; }; + 8DABC9AE1D3DA30D00453807 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; + 8DDF1AE41D3FF67D001F1160 /* ChatCollectionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatCollectionViewCell.swift; sourceTree = ""; }; + A31328708A7BD202B40D751C /* Pods-uidemoTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-uidemoTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-uidemoTests/Pods-uidemoTests.debug.xcconfig"; sourceTree = ""; }; + C9B65C9D0868F18A0BBFF783 /* Pods_uidemo.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_uidemo.framework; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 8DABC9821D3D82D600453807 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + DBA05234601E54F91320B659 /* Pods_uidemo.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 8DABC9961D3D82D600453807 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 6BEBFBD2CFCDA8A7918028C1 /* Pods_uidemoTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 8D643F0F1D4827F10081F979 /* ChatSample */ = { + isa = PBXGroup; + children = ( + 8DABC9AC1D3D9EAF00453807 /* ChatViewController.swift */, + 8DDF1AE41D3FF67D001F1160 /* ChatCollectionViewCell.swift */, + ); + name = ChatSample; + sourceTree = ""; + }; + 8DABC97C1D3D82D600453807 = { + isa = PBXGroup; + children = ( + 8DABC9871D3D82D600453807 /* uidemo */, + 8DABC99C1D3D82D600453807 /* uidemoTests */, + 8DABC9861D3D82D600453807 /* Products */, + F1E46E507352432313629425 /* Pods */, + BA2B6A95752296B2C731BC16 /* Frameworks */, + ); + sourceTree = ""; + }; + 8DABC9861D3D82D600453807 /* Products */ = { + isa = PBXGroup; + children = ( + 8DABC9851D3D82D600453807 /* uidemo.app */, + 8DABC9991D3D82D600453807 /* uidemoTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 8DABC9871D3D82D600453807 /* uidemo */ = { + isa = PBXGroup; + children = ( + 8DABC9881D3D82D600453807 /* AppDelegate.swift */, + 8DABC98A1D3D82D600453807 /* MenuViewController.swift */, + 8DABC9AA1D3D947300453807 /* SampleCell.swift */, + 8DABC9A81D3D872C00453807 /* Sample.swift */, + 8D643F0F1D4827F10081F979 /* ChatSample */, + 8DABC98C1D3D82D600453807 /* Main.storyboard */, + 8DABC98F1D3D82D600453807 /* Assets.xcassets */, + 8DABC9911D3D82D600453807 /* LaunchScreen.storyboard */, + 8DABC9941D3D82D600453807 /* Info.plist */, + 8DABC9AE1D3DA30D00453807 /* GoogleService-Info.plist */, + ); + path = uidemo; + sourceTree = ""; + }; + 8DABC99C1D3D82D600453807 /* uidemoTests */ = { + isa = PBXGroup; + children = ( + 8DABC99D1D3D82D600453807 /* uidemoTests.swift */, + 8DABC99F1D3D82D600453807 /* Info.plist */, + ); + path = uidemoTests; + sourceTree = ""; + }; + BA2B6A95752296B2C731BC16 /* Frameworks */ = { + isa = PBXGroup; + children = ( + C9B65C9D0868F18A0BBFF783 /* Pods_uidemo.framework */, + 1A128E4DD412913B07E20C37 /* Pods_uidemoTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + F1E46E507352432313629425 /* Pods */ = { + isa = PBXGroup; + children = ( + 5F8797FA94AF0055875F8117 /* Pods-uidemo.debug.xcconfig */, + 8ADB97851843928A14BC9024 /* Pods-uidemo.release.xcconfig */, + A31328708A7BD202B40D751C /* Pods-uidemoTests.debug.xcconfig */, + 6EC625C321DD275A92C4085C /* Pods-uidemoTests.release.xcconfig */, + ); + name = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 8DABC9841D3D82D600453807 /* uidemo */ = { + isa = PBXNativeTarget; + buildConfigurationList = 8DABC9A21D3D82D600453807 /* Build configuration list for PBXNativeTarget "uidemo" */; + buildPhases = ( + 757DE2C28DE66EF8748CA3DA /* [CP] Check Pods Manifest.lock */, + 8DABC9811D3D82D600453807 /* Sources */, + 8DABC9821D3D82D600453807 /* Frameworks */, + 8DABC9831D3D82D600453807 /* Resources */, + 766A34E36C2BABD9E53324E8 /* [CP] Embed Pods Frameworks */, + 6CC65E746C220141C88C7F20 /* [CP] Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = uidemo; + productName = uidemo; + productReference = 8DABC9851D3D82D600453807 /* uidemo.app */; + productType = "com.apple.product-type.application"; + }; + 8DABC9981D3D82D600453807 /* uidemoTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 8DABC9A51D3D82D600453807 /* Build configuration list for PBXNativeTarget "uidemoTests" */; + buildPhases = ( + 235E6CAB9EF32464B5D5C387 /* [CP] Check Pods Manifest.lock */, + 8DABC9951D3D82D600453807 /* Sources */, + 8DABC9961D3D82D600453807 /* Frameworks */, + 8DABC9971D3D82D600453807 /* Resources */, + C6C5C6CC234D3892828C1C9D /* [CP] Embed Pods Frameworks */, + 25A95440B19AD17F8CF61ED7 /* [CP] Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + 8DABC99B1D3D82D600453807 /* PBXTargetDependency */, + ); + name = uidemoTests; + productName = uidemoTests; + productReference = 8DABC9991D3D82D600453807 /* uidemoTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 8DABC97D1D3D82D600453807 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0730; + LastUpgradeCheck = 0730; + ORGANIZATIONNAME = morganchen; + TargetAttributes = { + 8DABC9841D3D82D600453807 = { + CreatedOnToolsVersion = 7.3.1; + }; + 8DABC9981D3D82D600453807 = { + CreatedOnToolsVersion = 7.3.1; + TestTargetID = 8DABC9841D3D82D600453807; + }; + }; + }; + buildConfigurationList = 8DABC9801D3D82D600453807 /* Build configuration list for PBXProject "uidemo" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 8DABC97C1D3D82D600453807; + productRefGroup = 8DABC9861D3D82D600453807 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 8DABC9841D3D82D600453807 /* uidemo */, + 8DABC9981D3D82D600453807 /* uidemoTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 8DABC9831D3D82D600453807 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 8DABC9931D3D82D600453807 /* LaunchScreen.storyboard in Resources */, + 8DABC9AF1D3DA30D00453807 /* GoogleService-Info.plist in Resources */, + 8DABC9901D3D82D600453807 /* Assets.xcassets in Resources */, + 8DABC98E1D3D82D600453807 /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 8DABC9971D3D82D600453807 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 235E6CAB9EF32464B5D5C387 /* [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; + }; + 25A95440B19AD17F8CF61ED7 /* [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-uidemoTests/Pods-uidemoTests-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + 6CC65E746C220141C88C7F20 /* [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-uidemo/Pods-uidemo-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + 757DE2C28DE66EF8748CA3DA /* [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; + }; + 766A34E36C2BABD9E53324E8 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-uidemo/Pods-uidemo-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + C6C5C6CC234D3892828C1C9D /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-uidemoTests/Pods-uidemoTests-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 8DABC9811D3D82D600453807 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 8DABC98B1D3D82D600453807 /* MenuViewController.swift in Sources */, + 8DABC9AB1D3D947300453807 /* SampleCell.swift in Sources */, + 8DABC9A91D3D872C00453807 /* Sample.swift in Sources */, + 8DABC9AD1D3D9EAF00453807 /* ChatViewController.swift in Sources */, + 8DDF1AE51D3FF67D001F1160 /* ChatCollectionViewCell.swift in Sources */, + 8DABC9891D3D82D600453807 /* AppDelegate.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 8DABC9951D3D82D600453807 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 8DABC99E1D3D82D600453807 /* uidemoTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 8DABC99B1D3D82D600453807 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 8DABC9841D3D82D600453807 /* uidemo */; + targetProxy = 8DABC99A1D3D82D600453807 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 8DABC98C1D3D82D600453807 /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 8DABC98D1D3D82D600453807 /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 8DABC9911D3D82D600453807 /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 8DABC9921D3D82D600453807 /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 8DABC9A01D3D82D600453807 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.3; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 8DABC9A11D3D82D600453807 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.3; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 8DABC9A31D3D82D600453807 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 5F8797FA94AF0055875F8117 /* Pods-uidemo.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + INFOPLIST_FILE = uidemo/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.firebase.uidemo; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 8DABC9A41D3D82D600453807 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 8ADB97851843928A14BC9024 /* Pods-uidemo.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + INFOPLIST_FILE = uidemo/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.firebase.uidemo; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = ""; + }; + name = Release; + }; + 8DABC9A61D3D82D600453807 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A31328708A7BD202B40D751C /* Pods-uidemoTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + INFOPLIST_FILE = uidemoTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.firebase.uidemoTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/uidemo.app/uidemo"; + }; + name = Debug; + }; + 8DABC9A71D3D82D600453807 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 6EC625C321DD275A92C4085C /* Pods-uidemoTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + INFOPLIST_FILE = uidemoTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.firebase.uidemoTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/uidemo.app/uidemo"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 8DABC9801D3D82D600453807 /* Build configuration list for PBXProject "uidemo" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 8DABC9A01D3D82D600453807 /* Debug */, + 8DABC9A11D3D82D600453807 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 8DABC9A21D3D82D600453807 /* Build configuration list for PBXNativeTarget "uidemo" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 8DABC9A31D3D82D600453807 /* Debug */, + 8DABC9A41D3D82D600453807 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 8DABC9A51D3D82D600453807 /* Build configuration list for PBXNativeTarget "uidemoTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 8DABC9A61D3D82D600453807 /* Debug */, + 8DABC9A71D3D82D600453807 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 8DABC97D1D3D82D600453807 /* Project object */; +} diff --git a/samples/swift/uidemo/AppDelegate.swift b/samples/swift/uidemo/AppDelegate.swift new file mode 100644 index 00000000000..e665a0a21ab --- /dev/null +++ b/samples/swift/uidemo/AppDelegate.swift @@ -0,0 +1,36 @@ +// +// 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. +// + +import UIKit +import Firebase + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate { + + static var mainStoryboard: UIStoryboard { + return UIStoryboard(name: "Main", bundle: nil) + } + + var window: UIWindow? + + func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { + // Successfully running this sample requires an app in Firebase and an + // accompanying valid GoogleService-Info.plist file. + FIRApp.configure() + return true + } +} + diff --git a/samples/swift/uidemo/Assets.xcassets/AppIcon.appiconset/Contents.json b/samples/swift/uidemo/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000000..eeea76c2db5 --- /dev/null +++ b/samples/swift/uidemo/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,73 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "3x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "83.5x83.5", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/samples/swift/uidemo/Base.lproj/LaunchScreen.storyboard b/samples/swift/uidemo/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 00000000000..2e721e1833f --- /dev/null +++ b/samples/swift/uidemo/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/swift/uidemo/Base.lproj/Main.storyboard b/samples/swift/uidemo/Base.lproj/Main.storyboard new file mode 100644 index 00000000000..08070d929ab --- /dev/null +++ b/samples/swift/uidemo/Base.lproj/Main.storyboard @@ -0,0 +1,212 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/swift/uidemo/ChatCollectionViewCell.swift b/samples/swift/uidemo/ChatCollectionViewCell.swift new file mode 100644 index 00000000000..0053763ef39 --- /dev/null +++ b/samples/swift/uidemo/ChatCollectionViewCell.swift @@ -0,0 +1,75 @@ +// +// 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. +// + +import UIKit + +class ChatCollectionViewCell: UICollectionViewCell { + @IBOutlet private(set) var textLabel: UILabel! { + didSet { + textLabel.font = ChatCollectionViewCell.messageFont + } + } + + static func boundingRectForText(text: String, maxWidth: CGFloat) -> CGRect { + let attributes = [NSFontAttributeName: ChatCollectionViewCell.messageFont] + let rect = text.boundingRectWithSize(CGSize(width: maxWidth, height: CGFloat.max), + options: [.UsesLineFragmentOrigin], + attributes: attributes, + context: nil) + return rect + } + + @IBOutlet var containerView: UIView! { + didSet { + containerView.layer.cornerRadius = 8 + containerView.layer.masksToBounds = true + } + } + + // These constraints are used to left- and right-align chat bubbles. + @IBOutlet private(set) var leadingConstraint: NSLayoutConstraint! { + didSet { + leadingConstraint.identifier = "leading constraint" + } + } + @IBOutlet private(set) var trailingConstraint: NSLayoutConstraint! { + didSet { + trailingConstraint.identifier = "trailing constraint" + } + } + + // This is the source of truth for the message font, + // overriding whatever is set in interface builder. + static var messageFont: UIFont { + return UIFont.systemFontOfSize(UIFont.systemFontSize()) + } + + // Colors for messages sent by the client. + static var selfColors: (background: UIColor, text: UIColor) { + return ( + background: UIColor(red: 21 / 255, green: 60 / 255, blue: 235 / 255, alpha: 1), + text: UIColor.whiteColor() + ) + } + + // Colors for messages received by the client. + static var othersColors: (background: UIColor, text: UIColor) { + return ( + background: UIColor(red: 230 / 255, green: 230 / 255, blue: 230 / 255, alpha: 1), + text: UIColor.blackColor() + ) + } +} diff --git a/samples/swift/uidemo/ChatViewController.swift b/samples/swift/uidemo/ChatViewController.swift new file mode 100644 index 00000000000..d66b3777fba --- /dev/null +++ b/samples/swift/uidemo/ChatViewController.swift @@ -0,0 +1,251 @@ +// +// 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. +// + +import UIKit +import Firebase +import FirebaseDatabaseUI +import FirebaseAuthUI + +struct Chat { + var uid: String + var name: String + var text: String + + var dictionary: [String: String] { + return [ + "uid" : self.uid, + "name": self.name, + "text": self.text, + ] + } + + init(uid: String, name: String, text: String) { + self.name = name; self.uid = uid; self.text = text + } + + init?(snapshot: FIRDataSnapshot) { + guard let dict = snapshot.value as? [String: String] else { return nil } + guard let name = dict["name"] else { return nil } + guard let uid = dict["uid"] else { return nil } + guard let text = dict["text"] else { return nil } + + self.name = name + self.uid = uid + self.text = text + } +} + +// View controller demonstrating using a FirebaseCollectionViewDataSource +// to populate a collection view with chat messages. The relevant code +// is in the call to `collectionViewDataSource.populateCellWithBlock`. +// +// All of the error handling in this controller is done with `fatalError`; +// please don't copy paste it into your production code. +class ChatViewController: UIViewController, UICollectionViewDelegateFlowLayout { + + private static let reuseIdentifier = "ChatCollectionViewCell" + + @IBOutlet private var collectionView: UICollectionView! + @IBOutlet private var textView: UITextView! { + didSet { + textView.layer.borderColor = UIColor.grayColor().colorWithAlphaComponent(0.5).CGColor + textView.layer.borderWidth = 1 + textView.layer.cornerRadius = 8 + textView.layer.masksToBounds = true + } + } + @IBOutlet private var sendButton: UIButton! + + // Used to shift view contents up when the keyboard appears. + @IBOutlet weak var bottomConstraint: NSLayoutConstraint! + + private let auth = FIRAuth.auth() + private let chatReference = FIRDatabase.database().reference().child("chats") + + private var collectionViewDataSource: FirebaseCollectionViewDataSource! = nil + + private var user: FIRUser? + private var query: FIRDatabaseQuery? + + static func fromStoryboard(storyboard: UIStoryboard = AppDelegate.mainStoryboard) -> ChatViewController { + return storyboard.instantiateViewControllerWithIdentifier("ChatViewController") as! ChatViewController + } + + override func viewDidLoad() { + super.viewDidLoad() + + self.collectionView.backgroundColor = UIColor.whiteColor() + self.collectionView.delegate = self + let layout = self.collectionView.collectionViewLayout as! UICollectionViewFlowLayout + layout.minimumInteritemSpacing = CGFloat.max + layout.minimumLineSpacing = 4 + + self.auth?.addAuthStateDidChangeListener { (auth, user) in + self.user = user + } + + self.sendButton.addTarget(self, action: #selector(didTapSend), forControlEvents: .TouchUpInside) + } + + override func viewDidAppear(animated: Bool) { + super.viewDidAppear(animated) + + self.auth?.signInAnonymouslyWithCompletion { (user, error) in + if let error = error { + fatalError("Sign in failed: \(error.localizedDescription)") + } + + self.query = self.chatReference.queryLimitedToLast(50) + + self.collectionViewDataSource = FirebaseCollectionViewDataSource(query: self.query!, + prototypeReuseIdentifier: ChatViewController.reuseIdentifier, + view: self.collectionView) + + // The initializer called above--though it takes a collection view-- + // doesn't actually set the collection view's data source, so if + // we don't set it before trying to populate our view our app will crash. + self.collectionView.dataSource = self.collectionViewDataSource + + self.collectionViewDataSource.populateCellWithBlock { (anyCell, data) in + guard let cell = anyCell as? ChatCollectionViewCell else { + fatalError("Unexpected collection view cell class \(anyCell.self)") + } + + let chat = Chat(snapshot: data as! FIRDataSnapshot)! + + cell.textLabel.text = chat.text + + let leftRightPadding: CGFloat = 24 + let rect = ChatCollectionViewCell.boundingRectForText(cell.textLabel.text ?? "", + maxWidth: self.view.frame.size.width) + let width = self.view.frame.size.width + let constant = max(width - rect.size.width - leftRightPadding, CGFloat.min) + if chat.uid == self.user?.uid ?? "" { + let colors = ChatCollectionViewCell.selfColors + cell.containerView.backgroundColor = colors.background + cell.textLabel.textColor = colors.text + cell.trailingConstraint.active = false + cell.leadingConstraint.constant = constant + cell.leadingConstraint.active = true + } else { + let colors = ChatCollectionViewCell.othersColors + cell.containerView.backgroundColor = colors.background + cell.textLabel.textColor = colors.text + cell.leadingConstraint.active = false + cell.trailingConstraint.constant = constant + cell.trailingConstraint.active = true + } + } + + // Unfortunately neither FirebaseArray nor FirebaseCollectionViewDataSource + // provide an event listener, so in order to scroll on new insertions we + // still need to use the query directly. + self.query!.observeEventType(.ChildAdded, withBlock: { [unowned self] _ in + self.scrollToBottom(animated: true) + }) + } + + // Notification boilerplate to handle keyboard appearance/disappearance + NSNotificationCenter.defaultCenter().addObserver(self, + selector: #selector(keyboardWillShow), + name: UIKeyboardWillShowNotification, + object: nil) + NSNotificationCenter.defaultCenter().addObserver(self, + selector: #selector(keyboardWillHide), + name: UIKeyboardWillHideNotification, + object: nil) + } + + override func viewWillDisappear(animated: Bool) { + super.viewWillDisappear(animated) + NSNotificationCenter.defaultCenter().removeObserver(self) + } + + deinit { + NSNotificationCenter.defaultCenter().removeObserver(self) + } + + @objc private func keyboardWillShow(notification: NSNotification) { + let userInfo = notification.userInfo! + let endFrameValue = userInfo[UIKeyboardFrameEndUserInfoKey] as! NSValue + let endHeight = endFrameValue.CGRectValue().size.height + + self.bottomConstraint.constant = endHeight + + let curve = UIViewAnimationCurve(rawValue: userInfo[UIKeyboardAnimationCurveUserInfoKey] as! Int)! + let duration = userInfo[UIKeyboardAnimationDurationUserInfoKey] as! Double + + UIView.setAnimationCurve(curve) + UIView.animateWithDuration(duration) { + self.view.layoutIfNeeded() + } + } + + @objc private func keyboardWillHide(notification: NSNotification) { + self.bottomConstraint.constant = 6 + + let userInfo = notification.userInfo! + let curve = UIViewAnimationCurve(rawValue: userInfo[UIKeyboardAnimationCurveUserInfoKey] as! Int)! + let duration = userInfo[UIKeyboardAnimationDurationUserInfoKey] as! Double + + UIView.setAnimationCurve(curve) + UIView.animateWithDuration(duration) { + self.view.layoutIfNeeded() + } + } + + @objc private func didTapSend(sender: AnyObject) { + guard let user = self.auth?.currentUser else { return } + let uid = user.uid + let name = "User " + (uid as NSString).substringToIndex(6) + let _text = self.textView.text as String? + guard let text = _text else { return } + if (text.isEmpty) { return } + + let chat = Chat(uid: uid, name: name, text: text) + + self.chatReference.childByAutoId().setValue(chat.dictionary) { (error, dbref) in + if let error = error { + fatalError("Failed to write message: \(error.localizedDescription)") + } + } + + self.textView.text = "" + } + + private func scrollToBottom(animated animated: Bool) { + // FirebaseArray's documentation online is pretty lacking. + let fbarray = self.collectionViewDataSource.array + let indexPath = NSIndexPath(forRow: Int(fbarray.count()) - 1, inSection: 0) + self.collectionView.scrollToItemAtIndexPath(indexPath, atScrollPosition: .Bottom, animated: animated) + } + + // MARK - UICollectionViewDelegateFlowLayout + + func collectionView(collectionView: UICollectionView, layout + collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize { + let heightPadding: CGFloat = 16 + + let width = self.view.frame.size.width + let blob = self.collectionViewDataSource.objectAtIndex(UInt(indexPath.row)) as! FIRDataSnapshot + let text = Chat(snapshot: blob)!.text + + let rect = ChatCollectionViewCell.boundingRectForText(text, maxWidth: width) + + let height = CGFloat(ceil(Double(rect.size.height))) + heightPadding + return CGSize(width: width, height: height) + } +} diff --git a/samples/swift/uidemo/Info.plist b/samples/swift/uidemo/Info.plist new file mode 100644 index 00000000000..40c6215d906 --- /dev/null +++ b/samples/swift/uidemo/Info.plist @@ -0,0 +1,47 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/samples/swift/uidemo/MenuViewController.swift b/samples/swift/uidemo/MenuViewController.swift new file mode 100644 index 00000000000..3523d7d2702 --- /dev/null +++ b/samples/swift/uidemo/MenuViewController.swift @@ -0,0 +1,62 @@ +// +// 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. +// + +import UIKit + +// This controller exists solely to list the samples we've defined thus far. +// Because all of that stuff is static and unchanging, if the app ever crashes +// in here it's probably a typo or some other small accident. +class MenuViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { + + private let reuseIdentifier = "MenuViewControllerCell" + + @IBOutlet private var tableView: UITableView! + + override func viewDidLoad() { + super.viewDidLoad() + self.tableView.delegate = self + self.tableView.dataSource = self + + // self-sizing cells + self.tableView.estimatedRowHeight = 85 + self.tableView.rowHeight = UITableViewAutomaticDimension + } + + // MARK: - UITableView Delegate + + func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { + let navController = self.navigationController! // assert nonnull + let targetController = ChatViewController.fromStoryboard() + + navController.pushViewController(targetController, animated: true) + } + + // MARK: - UITableView Data Source + + func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { + let sampleType = Sample(rawValue: indexPath.row)! + + let cell = self.tableView.dequeueReusableCellWithIdentifier(reuseIdentifier) as! SampleCell + + cell.configureWithType(sampleType) + + return cell + } + + func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return Sample.total + } +} diff --git a/samples/swift/uidemo/Sample.swift b/samples/swift/uidemo/Sample.swift new file mode 100644 index 00000000000..ed7e2ebc713 --- /dev/null +++ b/samples/swift/uidemo/Sample.swift @@ -0,0 +1,41 @@ +// +// 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. +// + +import UIKit + +// As we add more sample use cases to FirebaseUI, +// this enum will eventually grow into a catalogue +// of features. When adding a new sample, remember to +// increment the `total` property and add appropriate +// titles and subtitles. +enum Sample: Int, RawRepresentable { + + case Chat = 0 + + static var total: Int { + return 1 + } + + var labels: (title: String, subtitle: String) { + switch self { + case .Chat: + return ( + title: "Chat", + subtitle: "Demonstrates using a FirebaseCollectionViewDataSource to load data from Firebase Database into a UICollectionView for a basic chat app." + ) + } + } +} diff --git a/samples/swift/uidemo/SampleCell.swift b/samples/swift/uidemo/SampleCell.swift new file mode 100644 index 00000000000..0f67b744d0a --- /dev/null +++ b/samples/swift/uidemo/SampleCell.swift @@ -0,0 +1,48 @@ +// +// 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. +// + +import UIKit + +class SampleCell: UITableViewCell { + + @IBOutlet private var titleLabel: UILabel! + @IBOutlet private var subtitleLabel: UILabel! + + override convenience init(style: UITableViewCellStyle, reuseIdentifier: String?) { + self.init(reuseIdentifier: reuseIdentifier!) + } + + init(reuseIdentifier: String) { + super.init(style: .Default, reuseIdentifier: reuseIdentifier) + } + + convenience init(type: Sample, reuseIdentifier: String) { + self.init(reuseIdentifier: reuseIdentifier) + + self.configureWithType(type) + } + + func configureWithType(type: Sample) { + let labels = type.labels + self.titleLabel.text = labels.title + self.subtitleLabel.text = labels.subtitle + } + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + } + +} diff --git a/samples/swift/uidemoTests/Info.plist b/samples/swift/uidemoTests/Info.plist new file mode 100644 index 00000000000..ba72822e872 --- /dev/null +++ b/samples/swift/uidemoTests/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/samples/swift/uidemoTests/uidemoTests.swift b/samples/swift/uidemoTests/uidemoTests.swift new file mode 100644 index 00000000000..e69de29bb2d From daf1b3810c17835358d93e9eb9f3e054a15b563b Mon Sep 17 00:00:00 2001 From: Morgan Chen Date: Wed, 27 Jul 2016 11:07:29 -0700 Subject: [PATCH 02/14] add empty auth screen, change bundle id --- .../swift/uidemo.xcodeproj/project.pbxproj | 8 ++++-- samples/swift/uidemo/AuthViewController.swift | 15 +++++++++++ .../swift/uidemo/Base.lproj/Main.storyboard | 18 +++++++++++++ samples/swift/uidemo/MenuViewController.swift | 3 ++- samples/swift/uidemo/Sample.swift | 25 ++++++++++++++++--- 5 files changed, 62 insertions(+), 7 deletions(-) create mode 100644 samples/swift/uidemo/AuthViewController.swift diff --git a/samples/swift/uidemo.xcodeproj/project.pbxproj b/samples/swift/uidemo.xcodeproj/project.pbxproj index 8c79ebc467f..93045c60f62 100644 --- a/samples/swift/uidemo.xcodeproj/project.pbxproj +++ b/samples/swift/uidemo.xcodeproj/project.pbxproj @@ -8,6 +8,7 @@ /* Begin PBXBuildFile section */ 6BEBFBD2CFCDA8A7918028C1 /* Pods_uidemoTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1A128E4DD412913B07E20C37 /* Pods_uidemoTests.framework */; }; + 8D16073E1D492B200069E4F5 /* AuthViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D16073D1D492B200069E4F5 /* AuthViewController.swift */; }; 8DABC9891D3D82D600453807 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8DABC9881D3D82D600453807 /* AppDelegate.swift */; }; 8DABC98B1D3D82D600453807 /* MenuViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8DABC98A1D3D82D600453807 /* MenuViewController.swift */; }; 8DABC98E1D3D82D600453807 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8DABC98C1D3D82D600453807 /* Main.storyboard */; }; @@ -37,6 +38,7 @@ 5F8797FA94AF0055875F8117 /* Pods-uidemo.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-uidemo.debug.xcconfig"; path = "Pods/Target Support Files/Pods-uidemo/Pods-uidemo.debug.xcconfig"; sourceTree = ""; }; 6EC625C321DD275A92C4085C /* Pods-uidemoTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-uidemoTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-uidemoTests/Pods-uidemoTests.release.xcconfig"; sourceTree = ""; }; 8ADB97851843928A14BC9024 /* Pods-uidemo.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-uidemo.release.xcconfig"; path = "Pods/Target Support Files/Pods-uidemo/Pods-uidemo.release.xcconfig"; sourceTree = ""; }; + 8D16073D1D492B200069E4F5 /* AuthViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthViewController.swift; sourceTree = ""; }; 8DABC9851D3D82D600453807 /* uidemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = uidemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 8DABC9881D3D82D600453807 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 8DABC98A1D3D82D600453807 /* MenuViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuViewController.swift; sourceTree = ""; }; @@ -112,6 +114,7 @@ 8DABC98A1D3D82D600453807 /* MenuViewController.swift */, 8DABC9AA1D3D947300453807 /* SampleCell.swift */, 8DABC9A81D3D872C00453807 /* Sample.swift */, + 8D16073D1D492B200069E4F5 /* AuthViewController.swift */, 8D643F0F1D4827F10081F979 /* ChatSample */, 8DABC98C1D3D82D600453807 /* Main.storyboard */, 8DABC98F1D3D82D600453807 /* Assets.xcassets */, @@ -355,6 +358,7 @@ 8DABC98B1D3D82D600453807 /* MenuViewController.swift in Sources */, 8DABC9AB1D3D947300453807 /* SampleCell.swift in Sources */, 8DABC9A91D3D872C00453807 /* Sample.swift in Sources */, + 8D16073E1D492B200069E4F5 /* AuthViewController.swift in Sources */, 8DABC9AD1D3D9EAF00453807 /* ChatViewController.swift in Sources */, 8DDF1AE51D3FF67D001F1160 /* ChatCollectionViewCell.swift in Sources */, 8DABC9891D3D82D600453807 /* AppDelegate.swift in Sources */, @@ -492,7 +496,7 @@ CLANG_ENABLE_MODULES = YES; INFOPLIST_FILE = uidemo/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = com.firebase.uidemo; + PRODUCT_BUNDLE_IDENTIFIER = com.google.firebase.firebaseui.uidemo; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = ""; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -507,7 +511,7 @@ CLANG_ENABLE_MODULES = YES; INFOPLIST_FILE = uidemo/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = com.firebase.uidemo; + PRODUCT_BUNDLE_IDENTIFIER = com.google.firebase.firebaseui.uidemo; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = ""; }; diff --git a/samples/swift/uidemo/AuthViewController.swift b/samples/swift/uidemo/AuthViewController.swift new file mode 100644 index 00000000000..a519ce8fb68 --- /dev/null +++ b/samples/swift/uidemo/AuthViewController.swift @@ -0,0 +1,15 @@ +// +// AuthViewController.swift +// uidemo +// +// Created by Morgan Chen on 7/27/16. +// Copyright © 2016 morganchen. All rights reserved. +// + +import UIKit + +class AuthViewController: UIViewController { + static func fromStoryboard(storyboard: UIStoryboard = AppDelegate.mainStoryboard) -> AuthViewController { + return storyboard.instantiateViewControllerWithIdentifier("AuthViewController") as! AuthViewController + } +} diff --git a/samples/swift/uidemo/Base.lproj/Main.storyboard b/samples/swift/uidemo/Base.lproj/Main.storyboard index 08070d929ab..15671a660b9 100644 --- a/samples/swift/uidemo/Base.lproj/Main.storyboard +++ b/samples/swift/uidemo/Base.lproj/Main.storyboard @@ -190,6 +190,24 @@ + + + + + + + + + + + + + + + + + + diff --git a/samples/swift/uidemo/MenuViewController.swift b/samples/swift/uidemo/MenuViewController.swift index 3523d7d2702..be5d2dc923e 100644 --- a/samples/swift/uidemo/MenuViewController.swift +++ b/samples/swift/uidemo/MenuViewController.swift @@ -39,9 +39,10 @@ class MenuViewController: UIViewController, UITableViewDelegate, UITableViewData func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { let navController = self.navigationController! // assert nonnull - let targetController = ChatViewController.fromStoryboard() + let targetController = Sample(rawValue: indexPath.row)!.controller() navController.pushViewController(targetController, animated: true) + tableView.deselectRowAtIndexPath(indexPath, animated: true) } // MARK: - UITableView Data Source diff --git a/samples/swift/uidemo/Sample.swift b/samples/swift/uidemo/Sample.swift index ed7e2ebc713..176b0bff0b2 100644 --- a/samples/swift/uidemo/Sample.swift +++ b/samples/swift/uidemo/Sample.swift @@ -18,15 +18,18 @@ import UIKit // As we add more sample use cases to FirebaseUI, // this enum will eventually grow into a catalogue -// of features. When adding a new sample, remember to -// increment the `total` property and add appropriate -// titles and subtitles. +// of features. enum Sample: Int, RawRepresentable { case Chat = 0 + case Auth = 1 static var total: Int { - return 1 + var count = 0 + while let _ = Sample(rawValue: count) { + count += 1 + } + return count } var labels: (title: String, subtitle: String) { @@ -36,6 +39,20 @@ enum Sample: Int, RawRepresentable { title: "Chat", subtitle: "Demonstrates using a FirebaseCollectionViewDataSource to load data from Firebase Database into a UICollectionView for a basic chat app." ) + case .Auth: + return ( + title: "Auth", + subtitle: "Demonstrates the FirebaseAuthUI flow with customization options" + ) + } + } + + func controller() -> UIViewController { + switch self { + case .Chat: + return ChatViewController.fromStoryboard() + case .Auth: + return AuthViewController.fromStoryboard() } } } From 76fbc727dbb708a435002c66214289cd672ac3a7 Mon Sep 17 00:00:00 2001 From: Morgan Chen Date: Wed, 27 Jul 2016 11:14:28 -0700 Subject: [PATCH 03/14] add README to sample project --- samples/swift/README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 samples/swift/README.md diff --git a/samples/swift/README.md b/samples/swift/README.md new file mode 100644 index 00000000000..f5b5e950f32 --- /dev/null +++ b/samples/swift/README.md @@ -0,0 +1,10 @@ +Swift Samples +----- + +This directory contains a collection of swift code samples. + +In order to run the project you'll need a valid app in Firebase and +the `GoogleService-Info.plist` file for that project. Add the plist +to the `uidemo` folder (the one with all the class files) and the project +should build correctly. + From f24fd61f5cd12b346083cec9e7284445d58c7605 Mon Sep 17 00:00:00 2001 From: Morgan Chen Date: Wed, 27 Jul 2016 14:23:00 -0700 Subject: [PATCH 04/14] fiddle with comments --- samples/swift/uidemo/Sample.swift | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/samples/swift/uidemo/Sample.swift b/samples/swift/uidemo/Sample.swift index 176b0bff0b2..cca5cf1bdb0 100644 --- a/samples/swift/uidemo/Sample.swift +++ b/samples/swift/uidemo/Sample.swift @@ -16,11 +16,13 @@ import UIKit -// As we add more sample use cases to FirebaseUI, -// this enum will eventually grow into a catalogue -// of features. +// This enum represents the samples that this app knows about, and +// is used by the MenuViewController to layout all of the samples +// and display basic information about them. enum Sample: Int, RawRepresentable { + // When adding new samples, add a new value here and fill + // out the switch statements below as necessary. case Chat = 0 case Auth = 1 From 8a3afa3e3698c0a118a5e2d0be68c19571467c11 Mon Sep 17 00:00:00 2001 From: Morgan Chen Date: Wed, 27 Jul 2016 14:26:53 -0700 Subject: [PATCH 05/14] fix licensing header --- samples/swift/uidemo/AuthViewController.swift | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/samples/swift/uidemo/AuthViewController.swift b/samples/swift/uidemo/AuthViewController.swift index a519ce8fb68..6dcbb79cb71 100644 --- a/samples/swift/uidemo/AuthViewController.swift +++ b/samples/swift/uidemo/AuthViewController.swift @@ -1,9 +1,17 @@ // -// AuthViewController.swift -// uidemo +// Copyright (c) 2016 Google Inc. // -// Created by Morgan Chen on 7/27/16. -// Copyright © 2016 morganchen. All rights reserved. +// 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. // import UIKit From 6ddfd42f5d7f21f7f01615f88b3d5a12ef66bfd8 Mon Sep 17 00:00:00 2001 From: Morgan Chen Date: Wed, 27 Jul 2016 14:46:46 -0700 Subject: [PATCH 06/14] move cell layout logic out of controller, thank god --- .../swift/uidemo/ChatCollectionViewCell.swift | 52 ++++++++++++++----- samples/swift/uidemo/ChatViewController.swift | 41 +++------------ 2 files changed, 46 insertions(+), 47 deletions(-) diff --git a/samples/swift/uidemo/ChatCollectionViewCell.swift b/samples/swift/uidemo/ChatCollectionViewCell.swift index 0053763ef39..7f2579ba426 100644 --- a/samples/swift/uidemo/ChatCollectionViewCell.swift +++ b/samples/swift/uidemo/ChatCollectionViewCell.swift @@ -15,7 +15,9 @@ // import UIKit +import Firebase +/// Displays an individual chat message inside of a ChatViewController. class ChatCollectionViewCell: UICollectionViewCell { @IBOutlet private(set) var textLabel: UILabel! { didSet { @@ -40,24 +42,18 @@ class ChatCollectionViewCell: UICollectionViewCell { } // These constraints are used to left- and right-align chat bubbles. - @IBOutlet private(set) var leadingConstraint: NSLayoutConstraint! { - didSet { - leadingConstraint.identifier = "leading constraint" - } - } - @IBOutlet private(set) var trailingConstraint: NSLayoutConstraint! { - didSet { - trailingConstraint.identifier = "trailing constraint" - } - } + @IBOutlet private(set) var leadingConstraint: NSLayoutConstraint! + @IBOutlet private(set) var trailingConstraint: NSLayoutConstraint! - // This is the source of truth for the message font, - // overriding whatever is set in interface builder. + /// The font used to display chat messages. + /// This is the source of truth for the message font, + /// overriding whatever is set in interface builder. static var messageFont: UIFont { return UIFont.systemFontOfSize(UIFont.systemFontSize()) } - // Colors for messages sent by the client. + /// Colors for messages (text and background) sent from the client. + /// White text on a blue background, similar to the Messages app. static var selfColors: (background: UIColor, text: UIColor) { return ( background: UIColor(red: 21 / 255, green: 60 / 255, blue: 235 / 255, alpha: 1), @@ -65,11 +61,39 @@ class ChatCollectionViewCell: UICollectionViewCell { ) } - // Colors for messages received by the client. + /// Colors for messages received by the client. + /// Black text on a light gray background, similar to the Messages app. static var othersColors: (background: UIColor, text: UIColor) { return ( background: UIColor(red: 230 / 255, green: 230 / 255, blue: 230 / 255, alpha: 1), text: UIColor.blackColor() ) } + + /// Sets the cell's contents and lays out the cell according + /// to the contents set. + func populateCellWithChat(chat: Chat, user: FIRUser?, maxWidth: CGFloat) { + self.textLabel.text = chat.text + + let leftRightPadding: CGFloat = 24 + let rect = ChatCollectionViewCell.boundingRectForText(self.textLabel.text!, + maxWidth: maxWidth) + + let constant = max(maxWidth - rect.size.width - leftRightPadding, CGFloat.min) + if chat.uid == user?.uid ?? "" { + let colors = ChatCollectionViewCell.selfColors + self.containerView.backgroundColor = colors.background + self.textLabel.textColor = colors.text + self.trailingConstraint.active = false + self.leadingConstraint.constant = constant + self.leadingConstraint.active = true + } else { + let colors = ChatCollectionViewCell.othersColors + self.containerView.backgroundColor = colors.background + self.textLabel.textColor = colors.text + self.leadingConstraint.active = false + self.trailingConstraint.constant = constant + self.trailingConstraint.active = true + } + } } diff --git a/samples/swift/uidemo/ChatViewController.swift b/samples/swift/uidemo/ChatViewController.swift index d66b3777fba..65ec281fa7c 100644 --- a/samples/swift/uidemo/ChatViewController.swift +++ b/samples/swift/uidemo/ChatViewController.swift @@ -48,10 +48,10 @@ struct Chat { } } -// View controller demonstrating using a FirebaseCollectionViewDataSource -// to populate a collection view with chat messages. The relevant code -// is in the call to `collectionViewDataSource.populateCellWithBlock`. -// +/// View controller demonstrating using a FirebaseCollectionViewDataSource +/// to populate a collection view with chat messages. The relevant code +/// is in the call to `collectionViewDataSource.populateCellWithBlock`. +/// // All of the error handling in this controller is done with `fatalError`; // please don't copy paste it into your production code. class ChatViewController: UIViewController, UICollectionViewDelegateFlowLayout { @@ -125,29 +125,7 @@ class ChatViewController: UIViewController, UICollectionViewDelegateFlowLayout { } let chat = Chat(snapshot: data as! FIRDataSnapshot)! - - cell.textLabel.text = chat.text - - let leftRightPadding: CGFloat = 24 - let rect = ChatCollectionViewCell.boundingRectForText(cell.textLabel.text ?? "", - maxWidth: self.view.frame.size.width) - let width = self.view.frame.size.width - let constant = max(width - rect.size.width - leftRightPadding, CGFloat.min) - if chat.uid == self.user?.uid ?? "" { - let colors = ChatCollectionViewCell.selfColors - cell.containerView.backgroundColor = colors.background - cell.textLabel.textColor = colors.text - cell.trailingConstraint.active = false - cell.leadingConstraint.constant = constant - cell.leadingConstraint.active = true - } else { - let colors = ChatCollectionViewCell.othersColors - cell.containerView.backgroundColor = colors.background - cell.textLabel.textColor = colors.text - cell.leadingConstraint.active = false - cell.trailingConstraint.constant = constant - cell.trailingConstraint.active = true - } + cell.populateCellWithChat(chat, user: self.user, maxWidth: self.view.frame.size.width) } // Unfortunately neither FirebaseArray nor FirebaseCollectionViewDataSource @@ -169,15 +147,13 @@ class ChatViewController: UIViewController, UICollectionViewDelegateFlowLayout { object: nil) } + // MARK: - Boilerplate + override func viewWillDisappear(animated: Bool) { super.viewWillDisappear(animated) NSNotificationCenter.defaultCenter().removeObserver(self) } - deinit { - NSNotificationCenter.defaultCenter().removeObserver(self) - } - @objc private func keyboardWillShow(notification: NSNotification) { let userInfo = notification.userInfo! let endFrameValue = userInfo[UIKeyboardFrameEndUserInfoKey] as! NSValue @@ -227,13 +203,12 @@ class ChatViewController: UIViewController, UICollectionViewDelegateFlowLayout { } private func scrollToBottom(animated animated: Bool) { - // FirebaseArray's documentation online is pretty lacking. let fbarray = self.collectionViewDataSource.array let indexPath = NSIndexPath(forRow: Int(fbarray.count()) - 1, inSection: 0) self.collectionView.scrollToItemAtIndexPath(indexPath, atScrollPosition: .Bottom, animated: animated) } - // MARK - UICollectionViewDelegateFlowLayout + // MARK UICollectionViewDelegateFlowLayout func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize { From 099f866995870791359dc774cebb4536dcb0d0f4 Mon Sep 17 00:00:00 2001 From: Morgan Chen Date: Wed, 27 Jul 2016 14:48:38 -0700 Subject: [PATCH 07/14] move boilerplate to where no one will look --- .../swift/uidemo/ChatCollectionViewCell.swift | 2 +- samples/swift/uidemo/ChatViewController.swift | 160 +++++++++--------- 2 files changed, 84 insertions(+), 78 deletions(-) diff --git a/samples/swift/uidemo/ChatCollectionViewCell.swift b/samples/swift/uidemo/ChatCollectionViewCell.swift index 7f2579ba426..01d178df528 100644 --- a/samples/swift/uidemo/ChatCollectionViewCell.swift +++ b/samples/swift/uidemo/ChatCollectionViewCell.swift @@ -41,7 +41,7 @@ class ChatCollectionViewCell: UICollectionViewCell { } } - // These constraints are used to left- and right-align chat bubbles. + /// These constraints are used to left- and right-align chat bubbles. @IBOutlet private(set) var leadingConstraint: NSLayoutConstraint! @IBOutlet private(set) var trailingConstraint: NSLayoutConstraint! diff --git a/samples/swift/uidemo/ChatViewController.swift b/samples/swift/uidemo/ChatViewController.swift index 65ec281fa7c..4b0f08ac30f 100644 --- a/samples/swift/uidemo/ChatViewController.swift +++ b/samples/swift/uidemo/ChatViewController.swift @@ -19,42 +19,12 @@ import Firebase import FirebaseDatabaseUI import FirebaseAuthUI -struct Chat { - var uid: String - var name: String - var text: String - - var dictionary: [String: String] { - return [ - "uid" : self.uid, - "name": self.name, - "text": self.text, - ] - } - - init(uid: String, name: String, text: String) { - self.name = name; self.uid = uid; self.text = text - } - - init?(snapshot: FIRDataSnapshot) { - guard let dict = snapshot.value as? [String: String] else { return nil } - guard let name = dict["name"] else { return nil } - guard let uid = dict["uid"] else { return nil } - guard let text = dict["text"] else { return nil } - - self.name = name - self.uid = uid - self.text = text - } -} - /// View controller demonstrating using a FirebaseCollectionViewDataSource /// to populate a collection view with chat messages. The relevant code /// is in the call to `collectionViewDataSource.populateCellWithBlock`. -/// -// All of the error handling in this controller is done with `fatalError`; -// please don't copy paste it into your production code. class ChatViewController: UIViewController, UICollectionViewDelegateFlowLayout { + // All of the error handling in this controller is done with `fatalError`; + // please don't copy paste it into your production code. private static let reuseIdentifier = "ChatCollectionViewCell" @@ -69,8 +39,8 @@ class ChatViewController: UIViewController, UICollectionViewDelegateFlowLayout { } @IBOutlet private var sendButton: UIButton! - // Used to shift view contents up when the keyboard appears. - @IBOutlet weak var bottomConstraint: NSLayoutConstraint! + /// Used to shift view contents up when the keyboard appears. + @IBOutlet private var bottomConstraint: NSLayoutConstraint! private let auth = FIRAuth.auth() private let chatReference = FIRDatabase.database().reference().child("chats") @@ -80,43 +50,28 @@ class ChatViewController: UIViewController, UICollectionViewDelegateFlowLayout { private var user: FIRUser? private var query: FIRDatabaseQuery? - static func fromStoryboard(storyboard: UIStoryboard = AppDelegate.mainStoryboard) -> ChatViewController { - return storyboard.instantiateViewControllerWithIdentifier("ChatViewController") as! ChatViewController - } - - override func viewDidLoad() { - super.viewDidLoad() - - self.collectionView.backgroundColor = UIColor.whiteColor() - self.collectionView.delegate = self - let layout = self.collectionView.collectionViewLayout as! UICollectionViewFlowLayout - layout.minimumInteritemSpacing = CGFloat.max - layout.minimumLineSpacing = 4 - - self.auth?.addAuthStateDidChangeListener { (auth, user) in - self.user = user - } - - self.sendButton.addTarget(self, action: #selector(didTapSend), forControlEvents: .TouchUpInside) - } + // MARK: - Interesting stuff override func viewDidAppear(animated: Bool) { super.viewDidAppear(animated) self.auth?.signInAnonymouslyWithCompletion { (user, error) in if let error = error { + // An error here means the user couldn't sign in. Correctly + // handling it depends on the context as well as your app's + // capabilities, but this is usually a good place to + // present "retry" and "forgot your password?" screens. fatalError("Sign in failed: \(error.localizedDescription)") } self.query = self.chatReference.queryLimitedToLast(50) + // The initializer called below--though it takes a collection view-- + // doesn't actually set the collection view's data source, so if + // we don't set it before trying to populate our view our app will crash. self.collectionViewDataSource = FirebaseCollectionViewDataSource(query: self.query!, prototypeReuseIdentifier: ChatViewController.reuseIdentifier, view: self.collectionView) - - // The initializer called above--though it takes a collection view-- - // doesn't actually set the collection view's data source, so if - // we don't set it before trying to populate our view our app will crash. self.collectionView.dataSource = self.collectionViewDataSource self.collectionViewDataSource.populateCellWithBlock { (anyCell, data) in @@ -147,8 +102,49 @@ class ChatViewController: UIViewController, UICollectionViewDelegateFlowLayout { object: nil) } + @objc private func didTapSend(sender: AnyObject) { + guard let user = self.auth?.currentUser else { return } + let uid = user.uid + let name = "User " + (uid as NSString).substringToIndex(6) + let _text = self.textView.text as String? + guard let text = _text else { return } + if (text.isEmpty) { return } + + let chat = Chat(uid: uid, name: name, text: text) + + self.chatReference.childByAutoId().setValue(chat.dictionary) { (error, dbref) in + if let error = error { + // An error here most likely means the user doesn't have permission + // to chat (not signed in?) or the user has no internet connection. + fatalError("Failed to write message: \(error.localizedDescription)") + } + } + + self.textView.text = "" + } + // MARK: - Boilerplate + static func fromStoryboard(storyboard: UIStoryboard = AppDelegate.mainStoryboard) -> ChatViewController { + return storyboard.instantiateViewControllerWithIdentifier("ChatViewController") as! ChatViewController + } + + override func viewDidLoad() { + super.viewDidLoad() + + self.collectionView.backgroundColor = UIColor.whiteColor() + self.collectionView.delegate = self + let layout = self.collectionView.collectionViewLayout as! UICollectionViewFlowLayout + layout.minimumInteritemSpacing = CGFloat.max + layout.minimumLineSpacing = 4 + + self.auth?.addAuthStateDidChangeListener { (auth, user) in + self.user = user + } + + self.sendButton.addTarget(self, action: #selector(didTapSend), forControlEvents: .TouchUpInside) + } + override func viewWillDisappear(animated: Bool) { super.viewWillDisappear(animated) NSNotificationCenter.defaultCenter().removeObserver(self) @@ -183,32 +179,13 @@ class ChatViewController: UIViewController, UICollectionViewDelegateFlowLayout { } } - @objc private func didTapSend(sender: AnyObject) { - guard let user = self.auth?.currentUser else { return } - let uid = user.uid - let name = "User " + (uid as NSString).substringToIndex(6) - let _text = self.textView.text as String? - guard let text = _text else { return } - if (text.isEmpty) { return } - - let chat = Chat(uid: uid, name: name, text: text) - - self.chatReference.childByAutoId().setValue(chat.dictionary) { (error, dbref) in - if let error = error { - fatalError("Failed to write message: \(error.localizedDescription)") - } - } - - self.textView.text = "" - } - private func scrollToBottom(animated animated: Bool) { let fbarray = self.collectionViewDataSource.array let indexPath = NSIndexPath(forRow: Int(fbarray.count()) - 1, inSection: 0) self.collectionView.scrollToItemAtIndexPath(indexPath, atScrollPosition: .Bottom, animated: animated) } - // MARK UICollectionViewDelegateFlowLayout + // MARK: UICollectionViewDelegateFlowLayout func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize { @@ -224,3 +201,32 @@ class ChatViewController: UIViewController, UICollectionViewDelegateFlowLayout { return CGSize(width: width, height: height) } } + +struct Chat { + var uid: String + var name: String + var text: String + + var dictionary: [String: String] { + return [ + "uid" : self.uid, + "name": self.name, + "text": self.text, + ] + } + + init(uid: String, name: String, text: String) { + self.name = name; self.uid = uid; self.text = text + } + + init?(snapshot: FIRDataSnapshot) { + guard let dict = snapshot.value as? [String: String] else { return nil } + guard let name = dict["name"] else { return nil } + guard let uid = dict["uid"] else { return nil } + guard let text = dict["text"] else { return nil } + + self.name = name + self.uid = uid + self.text = text + } +} From 899df6e7621569b709787149ce4774d73d7f9397 Mon Sep 17 00:00:00 2001 From: Morgan Chen Date: Wed, 27 Jul 2016 16:15:36 -0700 Subject: [PATCH 08/14] update comments on data source event listener --- samples/swift/uidemo/ChatViewController.swift | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/samples/swift/uidemo/ChatViewController.swift b/samples/swift/uidemo/ChatViewController.swift index 4b0f08ac30f..93d875bb35d 100644 --- a/samples/swift/uidemo/ChatViewController.swift +++ b/samples/swift/uidemo/ChatViewController.swift @@ -83,9 +83,10 @@ class ChatViewController: UIViewController, UICollectionViewDelegateFlowLayout { cell.populateCellWithChat(chat, user: self.user, maxWidth: self.view.frame.size.width) } - // Unfortunately neither FirebaseArray nor FirebaseCollectionViewDataSource - // provide an event listener, so in order to scroll on new insertions we - // still need to use the query directly. + // FirebaseArray has a delegate method `childAdded` that could be used here, + // but unfortunately FirebaseCollectionViewDataSource uses the FirebaseArray + // delegate methods to update its own internal state, so in order to scroll + // on new insertions we still need to use the query directly. self.query!.observeEventType(.ChildAdded, withBlock: { [unowned self] _ in self.scrollToBottom(animated: true) }) @@ -180,8 +181,8 @@ class ChatViewController: UIViewController, UICollectionViewDelegateFlowLayout { } private func scrollToBottom(animated animated: Bool) { - let fbarray = self.collectionViewDataSource.array - let indexPath = NSIndexPath(forRow: Int(fbarray.count()) - 1, inSection: 0) + let count = self.collectionViewDataSource.collectionView(self.collectionView, numberOfItemsInSection: 0) + let indexPath = NSIndexPath(forRow: count - 1, inSection: 0) self.collectionView.scrollToItemAtIndexPath(indexPath, atScrollPosition: .Bottom, animated: animated) } From 56c67d91245e7886d7618cd793507e2a1cf8ce1d Mon Sep 17 00:00:00 2001 From: Morgan Chen Date: Wed, 27 Jul 2016 18:06:06 -0700 Subject: [PATCH 09/14] use swift subscript instead of nsstring substring --- samples/swift/uidemo/ChatViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/swift/uidemo/ChatViewController.swift b/samples/swift/uidemo/ChatViewController.swift index 93d875bb35d..3ec1b82950b 100644 --- a/samples/swift/uidemo/ChatViewController.swift +++ b/samples/swift/uidemo/ChatViewController.swift @@ -106,7 +106,7 @@ class ChatViewController: UIViewController, UICollectionViewDelegateFlowLayout { @objc private func didTapSend(sender: AnyObject) { guard let user = self.auth?.currentUser else { return } let uid = user.uid - let name = "User " + (uid as NSString).substringToIndex(6) + let name = "User " + uid[uid.characters.startIndex.. Date: Wed, 27 Jul 2016 18:06:48 -0700 Subject: [PATCH 10/14] deintegrate pods --- .../swift/uidemo.xcodeproj/project.pbxproj | 36 ------------------- 1 file changed, 36 deletions(-) diff --git a/samples/swift/uidemo.xcodeproj/project.pbxproj b/samples/swift/uidemo.xcodeproj/project.pbxproj index 93045c60f62..782f87a2990 100644 --- a/samples/swift/uidemo.xcodeproj/project.pbxproj +++ b/samples/swift/uidemo.xcodeproj/project.pbxproj @@ -7,7 +7,6 @@ objects = { /* Begin PBXBuildFile section */ - 6BEBFBD2CFCDA8A7918028C1 /* Pods_uidemoTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1A128E4DD412913B07E20C37 /* Pods_uidemoTests.framework */; }; 8D16073E1D492B200069E4F5 /* AuthViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D16073D1D492B200069E4F5 /* AuthViewController.swift */; }; 8DABC9891D3D82D600453807 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8DABC9881D3D82D600453807 /* AppDelegate.swift */; }; 8DABC98B1D3D82D600453807 /* MenuViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8DABC98A1D3D82D600453807 /* MenuViewController.swift */; }; @@ -20,7 +19,6 @@ 8DABC9AD1D3D9EAF00453807 /* ChatViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8DABC9AC1D3D9EAF00453807 /* ChatViewController.swift */; }; 8DABC9AF1D3DA30D00453807 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 8DABC9AE1D3DA30D00453807 /* GoogleService-Info.plist */; }; 8DDF1AE51D3FF67D001F1160 /* ChatCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8DDF1AE41D3FF67D001F1160 /* ChatCollectionViewCell.swift */; }; - DBA05234601E54F91320B659 /* Pods_uidemo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C9B65C9D0868F18A0BBFF783 /* Pods_uidemo.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -34,10 +32,6 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ - 1A128E4DD412913B07E20C37 /* Pods_uidemoTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_uidemoTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 5F8797FA94AF0055875F8117 /* Pods-uidemo.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-uidemo.debug.xcconfig"; path = "Pods/Target Support Files/Pods-uidemo/Pods-uidemo.debug.xcconfig"; sourceTree = ""; }; - 6EC625C321DD275A92C4085C /* Pods-uidemoTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-uidemoTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-uidemoTests/Pods-uidemoTests.release.xcconfig"; sourceTree = ""; }; - 8ADB97851843928A14BC9024 /* Pods-uidemo.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-uidemo.release.xcconfig"; path = "Pods/Target Support Files/Pods-uidemo/Pods-uidemo.release.xcconfig"; sourceTree = ""; }; 8D16073D1D492B200069E4F5 /* AuthViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthViewController.swift; sourceTree = ""; }; 8DABC9851D3D82D600453807 /* uidemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = uidemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 8DABC9881D3D82D600453807 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; @@ -54,8 +48,6 @@ 8DABC9AC1D3D9EAF00453807 /* ChatViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatViewController.swift; sourceTree = ""; }; 8DABC9AE1D3DA30D00453807 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; 8DDF1AE41D3FF67D001F1160 /* ChatCollectionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatCollectionViewCell.swift; sourceTree = ""; }; - A31328708A7BD202B40D751C /* Pods-uidemoTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-uidemoTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-uidemoTests/Pods-uidemoTests.debug.xcconfig"; sourceTree = ""; }; - C9B65C9D0868F18A0BBFF783 /* Pods_uidemo.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_uidemo.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -63,7 +55,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - DBA05234601E54F91320B659 /* Pods_uidemo.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -71,7 +62,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 6BEBFBD2CFCDA8A7918028C1 /* Pods_uidemoTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -93,8 +83,6 @@ 8DABC9871D3D82D600453807 /* uidemo */, 8DABC99C1D3D82D600453807 /* uidemoTests */, 8DABC9861D3D82D600453807 /* Products */, - F1E46E507352432313629425 /* Pods */, - BA2B6A95752296B2C731BC16 /* Frameworks */, ); sourceTree = ""; }; @@ -134,26 +122,6 @@ path = uidemoTests; sourceTree = ""; }; - BA2B6A95752296B2C731BC16 /* Frameworks */ = { - isa = PBXGroup; - children = ( - C9B65C9D0868F18A0BBFF783 /* Pods_uidemo.framework */, - 1A128E4DD412913B07E20C37 /* Pods_uidemoTests.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; - F1E46E507352432313629425 /* Pods */ = { - isa = PBXGroup; - children = ( - 5F8797FA94AF0055875F8117 /* Pods-uidemo.debug.xcconfig */, - 8ADB97851843928A14BC9024 /* Pods-uidemo.release.xcconfig */, - A31328708A7BD202B40D751C /* Pods-uidemoTests.debug.xcconfig */, - 6EC625C321DD275A92C4085C /* Pods-uidemoTests.release.xcconfig */, - ); - name = Pods; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -490,7 +458,6 @@ }; 8DABC9A31D3D82D600453807 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 5F8797FA94AF0055875F8117 /* Pods-uidemo.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; @@ -505,7 +472,6 @@ }; 8DABC9A41D3D82D600453807 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 8ADB97851843928A14BC9024 /* Pods-uidemo.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; @@ -519,7 +485,6 @@ }; 8DABC9A61D3D82D600453807 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = A31328708A7BD202B40D751C /* Pods-uidemoTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; INFOPLIST_FILE = uidemoTests/Info.plist; @@ -532,7 +497,6 @@ }; 8DABC9A71D3D82D600453807 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 6EC625C321DD275A92C4085C /* Pods-uidemoTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; INFOPLIST_FILE = uidemoTests/Info.plist; From d556156dcfeb97f802d0d9e6108e4963464f29aa Mon Sep 17 00:00:00 2001 From: Morgan Chen Date: Thu, 28 Jul 2016 10:40:30 -0700 Subject: [PATCH 11/14] use auth state change listener instead of completion closure --- samples/swift/uidemo/ChatViewController.swift | 36 ++++++++++--------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/samples/swift/uidemo/ChatViewController.swift b/samples/swift/uidemo/ChatViewController.swift index 3ec1b82950b..6291f546780 100644 --- a/samples/swift/uidemo/ChatViewController.swift +++ b/samples/swift/uidemo/ChatViewController.swift @@ -45,25 +45,20 @@ class ChatViewController: UIViewController, UICollectionViewDelegateFlowLayout { private let auth = FIRAuth.auth() private let chatReference = FIRDatabase.database().reference().child("chats") - private var collectionViewDataSource: FirebaseCollectionViewDataSource! = nil + private var collectionViewDataSource: FirebaseCollectionViewDataSource! private var user: FIRUser? private var query: FIRDatabaseQuery? + private var authStateListenerHandle: FIRAuthStateDidChangeListenerHandle? + // MARK: - Interesting stuff override func viewDidAppear(animated: Bool) { super.viewDidAppear(animated) - self.auth?.signInAnonymouslyWithCompletion { (user, error) in - if let error = error { - // An error here means the user couldn't sign in. Correctly - // handling it depends on the context as well as your app's - // capabilities, but this is usually a good place to - // present "retry" and "forgot your password?" screens. - fatalError("Sign in failed: \(error.localizedDescription)") - } - + self.authStateListenerHandle = self.auth?.addAuthStateDidChangeListener { (auth, user) in + self.user = user self.query = self.chatReference.queryLimitedToLast(50) // The initializer called below--though it takes a collection view-- @@ -85,11 +80,21 @@ class ChatViewController: UIViewController, UICollectionViewDelegateFlowLayout { // FirebaseArray has a delegate method `childAdded` that could be used here, // but unfortunately FirebaseCollectionViewDataSource uses the FirebaseArray - // delegate methods to update its own internal state, so in order to scroll + // delegate methods to update its own internal state, so in order to scroll // on new insertions we still need to use the query directly. self.query!.observeEventType(.ChildAdded, withBlock: { [unowned self] _ in self.scrollToBottom(animated: true) - }) + }) + } + + self.auth?.signInAnonymouslyWithCompletion { (user, error) in + if let error = error { + // An error here means the user couldn't sign in. Correctly + // handling it depends on the context as well as your app's + // capabilities, but this is usually a good place to + // present "retry" and "forgot your password?" screens. + fatalError("Sign in failed: \(error.localizedDescription)") + } } // Notification boilerplate to handle keyboard appearance/disappearance @@ -139,15 +144,14 @@ class ChatViewController: UIViewController, UICollectionViewDelegateFlowLayout { layout.minimumInteritemSpacing = CGFloat.max layout.minimumLineSpacing = 4 - self.auth?.addAuthStateDidChangeListener { (auth, user) in - self.user = user - } - self.sendButton.addTarget(self, action: #selector(didTapSend), forControlEvents: .TouchUpInside) } override func viewWillDisappear(animated: Bool) { super.viewWillDisappear(animated) + if let handle = self.authStateListenerHandle { + self.auth?.removeAuthStateDidChangeListener(handle) + } NSNotificationCenter.defaultCenter().removeObserver(self) } From 8d32a5a45a5639fbf19bd3fd0282da9af73d5a1d Mon Sep 17 00:00:00 2001 From: Morgan Chen Date: Thu, 28 Jul 2016 10:41:24 -0700 Subject: [PATCH 12/14] remove reference to GoogleService-Info.plist --- samples/swift/uidemo.xcodeproj/project.pbxproj | 4 ---- 1 file changed, 4 deletions(-) diff --git a/samples/swift/uidemo.xcodeproj/project.pbxproj b/samples/swift/uidemo.xcodeproj/project.pbxproj index 782f87a2990..e3980f13fc6 100644 --- a/samples/swift/uidemo.xcodeproj/project.pbxproj +++ b/samples/swift/uidemo.xcodeproj/project.pbxproj @@ -17,7 +17,6 @@ 8DABC9A91D3D872C00453807 /* Sample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8DABC9A81D3D872C00453807 /* Sample.swift */; }; 8DABC9AB1D3D947300453807 /* SampleCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8DABC9AA1D3D947300453807 /* SampleCell.swift */; }; 8DABC9AD1D3D9EAF00453807 /* ChatViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8DABC9AC1D3D9EAF00453807 /* ChatViewController.swift */; }; - 8DABC9AF1D3DA30D00453807 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 8DABC9AE1D3DA30D00453807 /* GoogleService-Info.plist */; }; 8DDF1AE51D3FF67D001F1160 /* ChatCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8DDF1AE41D3FF67D001F1160 /* ChatCollectionViewCell.swift */; }; /* End PBXBuildFile section */ @@ -46,7 +45,6 @@ 8DABC9A81D3D872C00453807 /* Sample.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Sample.swift; sourceTree = ""; }; 8DABC9AA1D3D947300453807 /* SampleCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SampleCell.swift; sourceTree = ""; }; 8DABC9AC1D3D9EAF00453807 /* ChatViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatViewController.swift; sourceTree = ""; }; - 8DABC9AE1D3DA30D00453807 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; 8DDF1AE41D3FF67D001F1160 /* ChatCollectionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatCollectionViewCell.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -108,7 +106,6 @@ 8DABC98F1D3D82D600453807 /* Assets.xcassets */, 8DABC9911D3D82D600453807 /* LaunchScreen.storyboard */, 8DABC9941D3D82D600453807 /* Info.plist */, - 8DABC9AE1D3DA30D00453807 /* GoogleService-Info.plist */, ); path = uidemo; sourceTree = ""; @@ -210,7 +207,6 @@ buildActionMask = 2147483647; files = ( 8DABC9931D3D82D600453807 /* LaunchScreen.storyboard in Resources */, - 8DABC9AF1D3DA30D00453807 /* GoogleService-Info.plist in Resources */, 8DABC9901D3D82D600453807 /* Assets.xcassets in Resources */, 8DABC98E1D3D82D600453807 /* Main.storyboard in Resources */, ); From 6735c7d56dec2bc1a8c9aae45c070e9c53ff666d Mon Sep 17 00:00:00 2001 From: Morgan Chen Date: Thu, 28 Jul 2016 10:45:07 -0700 Subject: [PATCH 13/14] amend readme --- samples/swift/README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/samples/swift/README.md b/samples/swift/README.md index f5b5e950f32..f8c40ec5686 100644 --- a/samples/swift/README.md +++ b/samples/swift/README.md @@ -4,7 +4,6 @@ Swift Samples This directory contains a collection of swift code samples. In order to run the project you'll need a valid app in Firebase and -the `GoogleService-Info.plist` file for that project. Add the plist -to the `uidemo` folder (the one with all the class files) and the project -should build correctly. +the `GoogleService-Info.plist` file for that project. Drag the plist into the +project root and the project should build correctly. From 666d473c71fcbbc6cabb9a5a501ba968bc342e0e Mon Sep 17 00:00:00 2001 From: Morgan Chen Date: Mon, 1 Aug 2016 10:24:20 -0700 Subject: [PATCH 14/14] add more stuff to samples readme --- samples/swift/README.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/samples/swift/README.md b/samples/swift/README.md index f8c40ec5686..e238e54bd75 100644 --- a/samples/swift/README.md +++ b/samples/swift/README.md @@ -5,5 +5,18 @@ This directory contains a collection of swift code samples. In order to run the project you'll need a valid app in Firebase and the `GoogleService-Info.plist` file for that project. Drag the plist into the -project root and the project should build correctly. +project root and the project should build correctly. Find more instructions +and download a plist file from the [Firebase console](https://console.firebase.google.com). + +###Chat Sample + +This sample uses [anonymous authentication](https://firebase.google.com/docs/auth/ios/anonymous-auth), +so make sure anonymous auth is enabled in Firebase console. + +###Auth Sample + +This sample uses [email/password](https://firebase.google.com/docs/auth/ios/password-auth), +[Google](https://firebase.google.com/docs/auth/ios/google-signin), +and [Facebook](https://firebase.google.com/docs/auth/ios/facebook-login) +auth, so make sure those are enabled in Firebase console.