aboutsummaryrefslogtreecommitdiffhomepage
path: root/Example/tvOSSample/tvOSSample/StorageViewController.swift
blob: 4416649c94cf8807ee3b309f7d58ce6ae6e62a52 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
// 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 FirebaseStorage

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)
    // TODO: Show progress bar here using proper API.
    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)
    }

    // The completion block above could be run before this line in some situations. 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. Ensure that Storage has an image uploaded to this path for this to
    /// function properly.
    static let downloadPath = "YOUR_IMAGE_NAME.jpg"

    static let maxSize: Int64 = 1024 * 1024 * 10 // ~10MB
  }
}