Using WebViews With Firebase Database - ios

Recently in my app I have been using Firebase to store information for my app and it has worked well. Now I am using it to stream videos with a web view being used in the tableview to display Youtube videos. When trying to link the WebView to the database, I get an error that says:
Type 'video' has no subscript members
What would be causing this?
Here is the code:
import UIKit
import Firebase
class videoController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var ref = DatabaseReference()
var video = [UIWebView]()
var databaseHandle:DatabaseHandle = 0
#IBOutlet var videoController: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
ref = Database.database().reference()
databaseHandle = ref.child("Videos").observe(.childAdded) { (snapshot) in
let post = snapshot.value as? UIWebView
if let actualPost = post {
self.video.append(actualPost)
self.videoController.reloadData()
}
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return video.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let video = tableView.dequeueReusableCell(withIdentifier: "video") as! video
video.videos.allowsInlineMediaPlayback = video[indexPath.row]
return(video)
}
}

This line:
let video = tableView.dequeueReusableCell(withIdentifier: "video") as! video
is your problem. This creates a new, local variable named video and it hides your video array property. Change it to:
let videoCell = tableView.dequeueReusableCell(withIdentifier: "video") as! video
Here's the whole method:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let videoCell = tableView.dequeueReusableCell(withIdentifier: "video") as! video
videoCell.videos.allowsInlineMediaPlayback = video[indexPath.row]
return videoCell
}
But on top of all of that, why do you have an array of web views? You certainly are not getting web views from Firebase.
And please fix your naming conventions. Class, struct, and enum names should start with uppercase letters. Variable, function, and case names start with lowercase letters. And use descriptive names. Naming everything simply video is confusing.
And change your video array to videos.

Related

How can I divide my table view data in sections alphabetically using Swift? (rewritten)

I have a data source in this form:
struct Country {
let name: String
}
The other properties won't come into play in this stage so let's keep it simple.
I have separated ViewController and TableViewDataSource in two separate files. Here is the Data source code:
class CountryDataSource: NSObject, UITableViewDataSource {
var countries = [Country]()
var filteredCountries = [Country]()
var dataChanged: (() -> Void)?
var tableView: UITableView!
let searchController = UISearchController(searchResultsController: nil)
var filterText: String? {
didSet {
filteredCountries = countries.matching(filterText)
self.dataChanged?()
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return filteredCountries.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
let country: Country
country = filteredCountries[indexPath.row]
cell.textLabel?.text = country.name
return cell
}
}
As you can see there is already a filtering mechanism in place.
Here is the most relevant part of the view controller:
class ViewController: UITableViewController, URLSessionDataDelegate {
let dataSource = CountryDataSource()
override func viewDidLoad() {
super.viewDidLoad()
dataSource.tableView = self.tableView
dataSource.dataChanged = { [weak self] in
self?.tableView.reloadData()
}
tableView.dataSource = dataSource
// Setup the Search Controller
dataSource.searchController.searchResultsUpdater = self
dataSource.searchController.obscuresBackgroundDuringPresentation = false
dataSource.searchController.searchBar.placeholder = "Search countries..."
navigationItem.searchController = dataSource.searchController
definesPresentationContext = true
performSelector(inBackground: #selector(loadCountries), with: nil)
}
The loadCountries is what fetches the JSON and load the table view inside the dataSource.countries and dataSource.filteredCountries array.
Now, how can I get the indexed collation like the Contacts app has without breaking all this?
I tried several tutorials, no one worked because they were needing a class data model or everything inside the view controller.
All solutions tried either crash (worst case) or don't load the correct data or don't recognise it...
Please I need some help here.
Thank you
I recommend you to work with CellViewModels instead of model data.
Steps:
1) Create an array per word with your cell view models sorted alphabetically. If you have data for A, C, F, L, Y and Z you are going to have 6 arrays with cell view models. I'm going to call them as "sectionArray".
2) Create another array and add the sectionArrays sorted alphabetically, the "cellModelsData". So, The cellModelsData is an array of sectionArrays.
3) On numberOfSections return the count of cellModelsData.
4) On numberOfRowsInSection get the sectionArray inside the cellModelsData according to the section number (cellModelsData[section]) and return the count of that sectionArray.
5) On cellForRowAtindexPath get the sectionArray (cellModelsData[indexPath.section]) and then get the "cellModel" (sectionArray[indexPath.row]). Dequeue the cell and set the cell model to the cell.
I think that this approach should resolve your problem.
I made a sample project in BitBucket that could help you: https://bitbucket.org/gastonmontes/reutilizablecellssampleproject
Example:
You have the following words:
Does.
Any.
Visa.
Count.
Refused.
Add.
Country.
1)
SectionArrayA: [Add, Any]
SectionArrayC: [Count, Country]
SectionArrayR: [Refused]
SectionArrayV: [Visa]
2)
cellModelsData = [ [SectionArrayA], [SectionArrayC], [SectionArrayR], [SectionArrayV] ]
3)
func numberOfSections(in tableView: UITableView) -> Int {
return self.cellModelsData.count
}
4)
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let sectionModels = self.cellModelsData[section]
return sectionModels.count
}
5)
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let sectionModels = self.cellModelsData[indexPath.section]
let cellModel = sectionModels[indexPath.row]
let cell = self.sampleCellsTableView.dequeueReusableCell(withIdentifier: "YourCellIdentifier",
for: indexPath) as! YourCell
cell.cellSetModel(cellModel)
return cell
}

Assign UITableView Cell label text from Swift dictionary

I am trying to get a value from a swift dictionary to display as cell texts. I am getting error:
Type 'Any' has no subscript members
cell.audioLabel.text = audiofiles["filetitle"] <-line producing error
I believe I may have set the variables incorrectly, the value is being passed using segue from another tableview using the didSelectRowAt.
var audios = Array<Any>() <- gets from another controller
This is my current viewcontroller code:
import UIKit
class DetailViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var audioTable: UITableView!
#IBOutlet weak var descText: UITextView!
#IBOutlet weak var clickButton: UIButton!
var practitle = String()
var desc = String()
var hasAudio = Int()
var audios = Array<Any>()
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.title = practitle
descText.text = desc
if hasAudio != 0 {
clickButton.isHidden = false
}
print(audios)
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return audios.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "audiocell", for: indexPath) as! DetailViewCell
let audiofiles = audios[indexPath.row]
cell.audioLabel.text = audiofiles["filetitle"]
return cell
}
When I use
print(audios)
The result that I get is:
[demoApp.Audiofile(id: 1, filetitle: "Sample file one", filename: "breath5mins", fileformat: "mp3"), demoApp.Audiofile(id: 2, filetitle: "Sample file two", filename: "breath10mins", fileformat: "mp3"), demoApp.Audiofile(id: 3, filetitle: "Sample file three", filename: "breath20mins", fileformat: "mp3")]
How can I use the filetitle as the label texts for my cell?
The goal is to display the title and open another view on cell click and allow the user to click a button on the new view and play the mp3 file.
Apparently the object is not a Swift dictionary, it's a custom class or struct.
You are fighting Swift's strong type system. Don't do that.
Declare audios with the static type
var audios = Array<AudioFile>()
and use dot notation instead of insecure KVC (which does not work anyway in this case).
cell.audioLabel.text = audiofiles.filetitle
You have declared your array as Array<Any> - which means that the array can contain anything; the elements of the array don't even all need to be the same type. As a result, the Swift compiler doesn't know what type of thing it is getting from audios[indexPath.row]; it could be a dictionary, it could be an array or it could be an integer. When you try and use subscripting, the compiler gives you an error because it doesn't know whether the item supports subscripting - i.e. an Int doesn't, and Any could be an Int.
Don't use Any or AnyObject in Swift if you know the actual type; Swift's type safety allows it to eliminate a large number of potential runtime issues at compile time by know what types things are.
From the print statement it appears that your array contains Audiofile instances (presumably a struct or class you have defined, not a dictionary). You should, therefore, declare audios correctly:
var audios = Array<Audiofile>()
You can then access the object's properties in your cellForRow:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "audiocell", for: indexPath) as! DetailViewCell
let audiofiles = audios[indexPath.row]
cell.audioLabel.text = audiofiles.filetitle
return cell
}
The reason you get the error is Any is an object with no definition. You can cast it into an AudioFile like this:
let audiofiles = audios[indexPath.row] as! AudioFile
If audios is an array of AudioFile only then declare it as such and you can use it the way you described.

UITableView.reloadData() is not refreshing tableView. No error message

I've already looked at the post UITableView.reloadData() is not working. I'm not sure that it applies to my situation, but let me know if I'm wrong.
My app has a tableView. From the main viewController I am opening another viewController, creating a new object, and then passing that object back to the original viewController, where it is added to an array called timers. All of that is working fine. However, when I call tableView.reloadData() in didUnwindFromNewTimerVC() to display the updated contents of the timers array, nothing happens.
NOTE: I have verified that the timers array is updated with the new object. Its count increments, and I can access its members. Everything else in didUnwindFromNewTimerVC() executes normally. The tableView just isn't updating to reflect it.
Here is my code:
import UIKit
class TimerListScreen: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tabelView: UITableView!
var timers = [Timer]()
let tableView = UITableView()
override func viewDidLoad() {
super.viewDidLoad()
tabelView.delegate = self
tabelView.dataSource = self
let tempTimer = Timer(timerLabel: "temp timer")
timers.append(tempTimer)
}
#IBAction func didUnwindFromNewTimerVC(_sender:UIStoryboardSegue){
guard let newTimerVC = _sender.source as? newTimerVC else{return}
newTimerVC.timer.setTimerLabel(timerLabel: newTimerVC.timerLabel.text!)
timers.append(newTimerVC.timer)
tableView.reloadData()
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tabelView.dequeueReusableCell(withIdentifier: "TimerCell", for: indexPath) as? TimerCell{
let timer = timers[indexPath.row]
cell.updateUI(Timer: timer)
return cell
}else{
return UITableViewCell()
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return timers.count
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 78
}
}
Thank you
Please note the spelling. There are two table view instances: the outlet tabelView and a (pointless) instance tableView.
Reload the data of the outlet
tabelView.reloadData()
and delete the declaration line of the second instance let tableView ....
However I'd recommend to rename the outlet to correctly spelled tableView (you might need to reconnect the outlet in Interface Builder).
And force unwrap the cell
let cell = tabelView.dequeueReusableCell(withIdentifier: "TimerCell", for: indexPath) as! TimerCell
and remove the if - else part. The code must not crash if everything is hooked up correctly in IB.

How should I manage correctly images provided by JSON

I'm trying to parse all the Image provided by downloaded JSON on my App. So, I've heard many ways to do that correctly. Which API should I used to manage the Images on my App? How should I do that, can I have an example?
I also wanted to take care correctly of delays between:
Run the app --> Load data --> Populate UI elements
What should I do to minimize this delay, I think a professional app shouldn't take that long to load all components.
That's the part where I'll populate a UITableView with Images.
var arrCerveja = [Cerveja]()
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
//TableView DataSource
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return arrCerveja.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellID") as! TableViewCell
let model = arrCerveja[indexPath.row]
cell.labelName.text = model.name
cell.labelDetail.text = "\(model.abv)"
cell. imageViewCell.image = ???? //How should I do that?
return cell
}
//TableView Delegate
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
}
override func viewDidLoad() {
super.viewDidLoad()
getApiData { (cerveja) in
arrCerveja = cerveja
self.tableView.reloadData()
}
}
Model Folder:
import Foundation
struct Cerveja:Decodable{
let name:String
let abv:Double
let image_url:String
}
Networking Folder:
import Alamofire
func getApiData(completion: #escaping ([Cerveja]) -> ()){
guard let urlString = URL(string: "https://api.punkapi.com/v2/beers") else {
print("URL Error")
return
}
Alamofire.request(urlString).responseJSON { response in
if response.data == response.data{
do{
let decoder = try JSONDecoder().decode([Cerveja].self, from: response.data!)
completion(decoder)
}catch{
print(error)
}
}else{print("API Response is Empty")}
}
}
What you can do is to cache the downloaded images, many libraries exist to help you do that, here is a list of some of them:
Kingfisher
HanekeSwift
Cache
Kingfisher is a good one that also allow you to download the images and explain you how to use the library with table views.
Caching the images will also reduce the loading time the next time the app is open.
You can also use this library to display a user friendly loading to the user during loading.

JSON response not being shown inside the cell

I’m creating a Tableview and trying to include in the cell one information that I receive through a JSON from an API.
The information (JSON) is being received very well and recorded correctly inside the variable.
However, what I could find is that as the information is received with a small “delay” is not being set as the cell’s label text at the cell creation moment, which is being set with the default variable content.
I guess the solution would be to update the label at the moment I parse the JSON content, right? How do I do this? (Update a cell’s label after it is already created)
Any other insight/ solution is greatly appreciated.
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, AddStock {
let operacoesAcoes = ListaOperacoes()
var todasAsOperacoes : [Operacao] = []
#IBOutlet weak var acoesTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
acoesTableView.delegate = self
acoesTableView.dataSource = self
acoesTableView.register(UINib(nibName: "StandardStockListCell", bundle: nil), forCellReuseIdentifier: "standardCell")
operacoesAcoes.consolidaAcoes()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return operacoesAcoes.carteiraAcoes.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "standardCell", for: indexPath) as! StandardStockListCell
let informacaoCompletaAcao = operacoesAcoes.carteiraAcoes[indexPath.row]
cell.codigoTextField.text = informacaoCompletaAcao.codigoAcao
cell.nomeTextField.text = informacaoCompletaAcao.nomeAcao
cell.quantidadeTotal.text = String(informacaoCompletaAcao.quantidadeTotal)
cell.precoMedioLabel.text = String(format: "%.2f", informacaoCompletaAcao.precoMedio)
//
// This is the part of the code that should set one label with a value returned from "buscaCotacao" but it does not work
// because at the time the cell is displayed it is still not updated from JSON information:
// Note: the buscaCotacao func is working fine
cell.precoAtualLabel.text = buscaCotacao(ativo: informacaoCompletaAcao.codigoAcao)
return cell
}
You need to reload the table view on the main thread after receiving and parsing the JSON.
self.acoesTableView.reloadData()
I did some research and tryouts, and could figure out a very simple (and now obvious) solution to update my Label after the result of the request is received:
- I call the function which retrieves the information from an API to update the cell ("buscaCotacao"), including the [cell row] information
- I update the cell's label from inside the function, which will happen only after the reply is received:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "standardCell", for: indexPath) as! StandardStockListCell
let informacaoCompletaAcao = operacoesAcoes.carteiraAcoes[indexPath.row]
cell.codigoTextField.text = informacaoCompletaAcao.codigoAcao
cell.nomeTextField.text = informacaoCompletaAcao.nomeAcao
cell.quantidadeTotal.text = String(informacaoCompletaAcao.quantidadeTotal)
cell.precoMedioLabel.text = "R$ "+String(format: "%.2f", informacaoCompletaAcao.precoMedio)
buscaCotacao(ativo: informacaoCompletaAcao.codigoAcao, for: cell, indexPath: indexPath)
return cell
}
And in the function:
func buscaCotacao (ativo: String, for cell: StandardStockListCell, indexPath: IndexPath) {
let finalURL = "https://www.alphavantage.co/query?function=TIME_SERIES_INTRADAY&interval=1min&apikey=XXXXXXXXXXXXXX&outputsize=compact&symbol=" + ativo
Alamofire.request(finalURL, method: .get)
.responseJSON { response in
if response.result.isSuccess {
let resultadoJSON : JSON = JSON(response.result.value!)
let resultado = Float (self.parseResultado(json: resultadoJSON))!
cell.precoAtualLabel.text = "R$ "+String(format: "%.2f", resultado)
self.cotacoes[ativo] = resultado
} else {
cell.precoAtualLabel.text = "N/D"
print("Error: \(response.result.error!)")
}
}
}

Resources