diff --git a/Example/tvOSSample/tvOSSample.xcodeproj/project.pbxproj b/Example/tvOSSample/tvOSSample.xcodeproj/project.pbxproj index 31f42b5aeab..70887ec1e7a 100644 --- a/Example/tvOSSample/tvOSSample.xcodeproj/project.pbxproj +++ b/Example/tvOSSample/tvOSSample.xcodeproj/project.pbxproj @@ -9,10 +9,10 @@ /* Begin PBXBuildFile section */ 73BE40BD370A0432D0B488E9 /* Pods_tvOSSample.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D2D69B1FAD502CB31A69F61D /* Pods_tvOSSample.framework */; }; ED4D5FDD1FBA008200501573 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED4D5FDC1FBA008200501573 /* AppDelegate.swift */; }; - ED4D5FDF1FBA008200501573 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED4D5FDE1FBA008200501573 /* ViewController.swift */; }; ED4D5FE21FBA008200501573 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = ED4D5FE01FBA008200501573 /* Main.storyboard */; }; ED4D5FE41FBA008200501573 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = ED4D5FE31FBA008200501573 /* Assets.xcassets */; }; ED4D5FEC1FBA055300501573 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = ED4D5FEB1FBA055300501573 /* GoogleService-Info.plist */; }; + ED822C851FBA212600B00A2F /* StorageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED822C841FBA212600B00A2F /* StorageViewController.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -21,11 +21,11 @@ D2D69B1FAD502CB31A69F61D /* Pods_tvOSSample.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_tvOSSample.framework; sourceTree = BUILT_PRODUCTS_DIR; }; ED4D5FD91FBA008200501573 /* tvOSSample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = tvOSSample.app; sourceTree = BUILT_PRODUCTS_DIR; }; ED4D5FDC1FBA008200501573 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - ED4D5FDE1FBA008200501573 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; ED4D5FE11FBA008200501573 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; ED4D5FE31FBA008200501573 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; ED4D5FE51FBA008300501573 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; ED4D5FEB1FBA055300501573 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; + ED822C841FBA212600B00A2F /* StorageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageViewController.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -80,10 +80,10 @@ children = ( ED4D5FEB1FBA055300501573 /* GoogleService-Info.plist */, ED4D5FDC1FBA008200501573 /* AppDelegate.swift */, - ED4D5FDE1FBA008200501573 /* ViewController.swift */, ED4D5FE01FBA008200501573 /* Main.storyboard */, ED4D5FE31FBA008200501573 /* Assets.xcassets */, ED4D5FE51FBA008300501573 /* Info.plist */, + ED822C841FBA212600B00A2F /* StorageViewController.swift */, ); path = tvOSSample; sourceTree = ""; @@ -221,8 +221,8 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - ED4D5FDF1FBA008200501573 /* ViewController.swift in Sources */, ED4D5FDD1FBA008200501573 /* AppDelegate.swift in Sources */, + ED822C851FBA212600B00A2F /* StorageViewController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Example/tvOSSample/tvOSSample/AppDelegate.swift b/Example/tvOSSample/tvOSSample/AppDelegate.swift index 0075efa7900..769e6e055a8 100644 --- a/Example/tvOSSample/tvOSSample/AppDelegate.swift +++ b/Example/tvOSSample/tvOSSample/AppDelegate.swift @@ -20,35 +20,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. FirebaseApp.configure() return true } - - func applicationWillResignActive(_ application: UIApplication) { - // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. - // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. - } - - func applicationDidEnterBackground(_ application: UIApplication) { - // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. - // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. - } - - func applicationWillEnterForeground(_ application: UIApplication) { - // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. - } - - func applicationDidBecomeActive(_ application: UIApplication) { - // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. - } - - func applicationWillTerminate(_ application: UIApplication) { - // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. - } - - } diff --git a/Example/tvOSSample/tvOSSample/Base.lproj/Main.storyboard b/Example/tvOSSample/tvOSSample/Base.lproj/Main.storyboard index fe9c6178686..ab142d999f1 100644 --- a/Example/tvOSSample/tvOSSample/Base.lproj/Main.storyboard +++ b/Example/tvOSSample/tvOSSample/Base.lproj/Main.storyboard @@ -1,5 +1,5 @@ - + @@ -10,10 +10,10 @@ - + - + @@ -22,23 +22,87 @@ - + + + + + + + + + + + + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Example/tvOSSample/tvOSSample/StorageViewController.swift b/Example/tvOSSample/tvOSSample/StorageViewController.swift new file mode 100644 index 00000000000..2402cf93aa1 --- /dev/null +++ b/Example/tvOSSample/tvOSSample/StorageViewController.swift @@ -0,0 +1,149 @@ +// Copyright 2017 Google +// +// 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 FirebaseCommunity + +class StorageViewController: UIViewController { + /// An enum describing the different states of the view controller. + private enum UIState: Equatable { + /// No image is being shown, waiting on user action. + case cleared + + /// Currently downloading from Firebase. + case downloading(StorageTask) + + /// The image has downloaded and should be displayed. + case downloaded(UIImage) + + /// Show an error message and stop downloading. + case failed(String) + + /// Equatable support for UIState. + static func ==(lhs: StorageViewController.UIState, rhs: StorageViewController.UIState) -> Bool { + switch (lhs, rhs) { + case (.cleared, .cleared): return true + case (.downloading, .downloading): return true + case (.downloaded, .downloaded): return true + case (.failed, .failed): return true + default: return false + } + } + } + + /// MARK: - Properties + + /// The current internal state of the view controller. + private var state: UIState = .cleared { + didSet { changeState(from: oldValue, to: state) } + } + + // MARK: Interface + + /// Image view to display the downloaded image. + @IBOutlet weak var imageView: UIImageView! + + /// The download button. + @IBOutlet weak var downloadButton: UIButton! + + /// The clear button. + @IBOutlet weak var clearButton: UIButton! + + /// A visual representation of the state. + @IBOutlet weak var stateLabel: UILabel! + + // MARK: - User Actions + + @IBAction func downloadButtonHit(_ sender: UIButton) { + guard case .cleared = state else { return } + + // Start the download! + let storage = Storage.storage() + let ref = storage.reference(withPath: Constants.downloadPath) + let task = ref.getData(maxSize: Constants.maxSize) { [unowned self] (data, error) in + guard let data = data else { + self.state = .failed("Error downloading: \(error!.localizedDescription)") + return + } + + // Create a UIImage from the PNG data. + guard let image = UIImage(data: data) else { + self.state = .failed("Unable to initialize image with data downloaded.") + return + } + + self.state = .downloaded(image) + } + + // We could have the image cached already causing the completion block to run before this line. + // If that's the case, we don't need to do anything else and can return. + if case .downloaded = state { return } + + // Set the state to downloading! + state = .downloading(task) + } + + @IBAction func clearButtonHit(_ sender: UIButton) { + guard case .downloaded = state else { return } + + state = .cleared + } + + // MARK: - State Management + + /// Changing from old state to new state. + private func changeState(from oldState: UIState, to newState: UIState) { + if oldState == newState { return } + + switch (oldState, newState) { + // Regular state, start downloading the image. + case (.cleared, .downloading(_)): + // TODO: Update the UI with a spinner? Progress update? + stateLabel.text = "State: Downloading..." + + // Download complete, ensure the download button is still off and enable the clear button. + case (_, .downloaded(let image)): + imageView.image = image + stateLabel.text = "State: Image downloaded!" + + // Clear everything and reset to the original state. + case (_, .cleared): + imageView.image = nil + stateLabel.text = "State: Pending download" + + // An error occurred. + case (_, .failed(let error)): + stateLabel.text = "State: \(error)" + + // For now, as the default, throw a fatal error because it's an unexpected state. This will + // allow us to catch it immediately and add the required action or fix the bug. + default: + fatalError("Programmer error! Tried to go from \(oldState) to \(newState)") + } + } + + // MARK: - Constants + + /// Internal constants for this class. + private struct Constants { + /// The image name to download. Can comment this out and replace it with the other below it as + /// part of the demo. + static let downloadPath = "yosemite.jpg" +// static let downloadPath = "google_io_2017.jpg" +// static let downloadPath = "firebase_logo.png" + + static let maxSize: Int64 = 1024 * 1024 * 10 // ~10MB + } +} diff --git a/Example/tvOSSample/tvOSSample/ViewController.swift b/Example/tvOSSample/tvOSSample/ViewController.swift deleted file mode 100644 index 4114f0d7c7c..00000000000 --- a/Example/tvOSSample/tvOSSample/ViewController.swift +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2017 Google -// -// 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 FirebaseCommunity - -class ViewController: UIViewController { - - override func viewDidLoad() { - super.viewDidLoad() - // Do any additional setup after loading the view, typically from a nib. - - } - - override func didReceiveMemoryWarning() { - super.didReceiveMemoryWarning() - // Dispose of any resources that can be recreated. - } - - -} -