I faced such problem. When I launch the ios application, I get a white screen and the data that I take from Firebase is not displayed. How can i fix this problem? I would be grateful for your favorite recommendations for solving my problem
This is my ViewController
class ViewController: UIViewController {
#IBOutlet weak var cv: UICollectionView!
var channel = [Channel]()
override func viewDidLoad() {
super.viewDidLoad()
self.cv.delegate = self
self.cv.dataSource = self
let db = Firestore.firestore()
db.collection("content").getDocuments() {( quarySnapshot, err) in
if let err = err {
print("error")
} else {
for document in quarySnapshot!.documents {
if let name = document.data()["title"] as? Channel {
self.channel.append(name)
}
if let subtitle = document.data()["subtitle"] as? Channel {
self.channel.append(subtitle)
}
}
self.cv.reloadData()
}
}
}
}
extension ViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return channel.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! ContentCell
let channel = channel[indexPath.row]
cell.setup(channel: channel)
return cell
}
}
This is my Model
struct Content {
let contents: [Channel]
}
struct Channel {
let title: String
let subtitle: String
}
This is my Cell
class ContentCell: UICollectionViewCell {
#IBOutlet weak var channelText: UILabel!
#IBOutlet weak var subtitle: UITextView!
func setup(channel: Channel) {
channelText.text = channel.title
subtitle.text = channel.subtitle
}
}
The data retrieved from Firestore can't just magically be cast to your custom type (Channel); it's a simple dictionary. You eighter need to use Codable or do it manually like so:
I can't tell how exactly to convert it as you have not shared the structure of your data in Firestore, but I assume this will work:
db.collection("content").getDocuments() { (snapshot, error) in
if let error = error {
print("error: \(error.localizedDescription)")
} else if let snapshot = snapshot {
for document in snapshot.documents {
let data = document.data()
if let title = data["title"] as? String,
let subtitle = data["subtitle"] as? String {
self.channel.append(Channel(title: title, subtitle: subtitle))
}
}
}
self.cv.reloadData()
}
Related
I am trying to learn firebase.I have two folders on firebase
1)VDBackgroundFrames/
2)VDFrames/
In both folders ,we have 4 images - VDBG2.png,VDBG3.png,VDBG4.png,VDBG5.png.
I am able to access one image at a time from firebase using the following code:-
func firebaseSetUp(){
let store = Storage.storage()
let storeRef = store.reference()
let userProfilesRef = storeRef.child("VDBackgroundFrames/VDBG11.jpg")
userProfilesRef.downloadURL { (url,error) in
if error != nil {
print("error?.localizedDescription",error?.localizedDescription)
return
}else{
print("url",url!)
}
}
}
//==========updated code ====//
func firebaseSetUp(){
let store = Storage.storage()
let storeRef = store.reference()
let userProfilesRef = storeRef.child("VDBackgroundFrames/")
userProfilesRef.observe(.childAdded, with: { [weak self] (snapshot) -> Void in
guard let strongSelf = self else { return }
//Logic to extract urls...
}, changeHandler: (StorageReference, NSKeyValueObservedChange<Value>) -> Void)
}
Output that I am obtaining is as follows:-
URL
https://firebasestorage.googleapis.com/v0/b/celebrations-8edf8.appspot.com/o/VDBackgroundFrames%2FVDBG11.jpg?alt=media&token=ae0910d1-2139-4443-b19a-02edde2f9b17
I actually want to access all the 4 images together from folder VDBackgroundFrames & VDFrames respectively.Kindly suggest the possible way to do it.Any suggestion or guidance would be apprecialble.
Thanks in advance.
Just access the root folder instead of the child directly, that way you'll obtain all the nodes/images in that folder something like this:
func firebaseSetUp(){
let store = Storage.storage()
let storeRef = store.reference()
let userProfilesRef = storeRef.child("VDBackgroundFrames/")
userProfilesRef.observe(.childAdded, with: { [weak self] (snapshot) -> Void in
guard let strongSelf = self else { return }
//Logic to extract urls...
}
}
DownloadURL takes single string at a time. In case you want to show all the files inside a folder to a tableview like me, here is the full code:
import UIKit import Firebase
My very First View Controller-
class FolderList: UIViewController {
var folderList: [StorageReference]?
lazy var storage = Storage.storage()
#IBOutlet weak var tableView : UITableView!
override func viewDidLoad() {
super.viewDidLoad()
self.storage.reference().child("TestFolder").listAll(completion: {
(result,error) in
print("result is \(result)")
self.folderList = result.items
DispatchQueue.main.async {
self.tableView.reloadData()
}
})
} }
extension FolderList : UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return folderList?.count ?? 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "FolderListCell", for:
indexPath) as? FolderListCell else {return UITableViewCell()}
cell.itemName.text = folderList?[indexPath.row].name
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 64.0
} }
extension FolderList : UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let storyBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
guard let downloadVC = storyBoard.instantiateViewController(withIdentifier:
"DownloadedItemView") as? DownloadedItemView else {
return
}
downloadVC.storageRef = folderList?[indexPath.row]
self.navigationController?.pushViewController(downloadVC, animated: true)
}
}
And here is you DownloadedItemView, which will open the images you selected from the list in a view:
import UIKit
import WebKit
import Firebase
class DownloadedItemView: UIViewController {
#IBOutlet weak var webView : WKWebView!
var downloadItemURL : String?
var storageRef : StorageReference?
override func viewDidLoad() {
super.viewDidLoad()
storageRef?.downloadURL(completion: {(downloadURL,error) in
print("url is \(downloadURL)")
DispatchQueue.main.async {
guard let url = downloadURL else {return}
let urlrequest = URLRequest(url: url)
self.webView.load(urlrequest)
}
})
}
}
Your each cell:
class FolderListCell: UITableViewCell {
#IBOutlet weak var itemName : UILabel!
}
I have the following code using RxSwift:
self.photos
.bind(to: collectionView.rx.items(dataSource: self.dataSource))
.disposed(by: disposeBag)
And it gives me Type of expression is ambiguous without more context
What more context does it need?
The complete code is shown below:
//
// PhotosCollectionViewController.swift
// TodoListRxSwift
//
//
import Foundation
import UIKit
import RxSwift
import RxCocoa
import RxDataSources
struct Photo {
var name :String
var imageURL :String
}
struct SectionOfPhoto {
var header: String
var items: [Photo]
}
extension SectionOfPhoto: SectionModelType {
init(original: SectionOfPhoto, items: [Photo]) {
self = original
self.items = items
}
}
class PhotosCollectionViewController :UICollectionViewController {
private let disposeBag = DisposeBag()
private (set) var photos = BehaviorRelay(value: [Photo(name: "Pic 1", imageURL: "1.png"),Photo(name: "Pic 2", imageURL: "2.png"),Photo(name: "Pic 3", imageURL: "3.png")])
let dataSource = RxCollectionViewSectionedReloadDataSource<SectionOfPhoto>(configureCell: { ds, cv, indexPath, photo in
let cell = cv.dequeueReusableCell(withReuseIdentifier: "PhotoCollectionViewCell", for: indexPath)
return cell
})
override func viewDidLoad() {
super.viewDidLoad()
self.collectionView?.delegate = nil
self.collectionView?.dataSource = nil
configureObservables()
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
prepareSegueForAddPhotoViewController(segue :segue)
}
override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
switch kind {
case UICollectionElementKindSectionHeader:
let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionElementKindSectionHeader, withReuseIdentifier: "PhotosHeaderView", for: indexPath)
return headerView
default:
return UICollectionReusableView()
}
}
private func prepareSegueForAddPhotoViewController(segue :UIStoryboardSegue) {
guard let nc = segue.destination as? UINavigationController else {
fatalError("NavigationController does not exist")
}
guard let addPhotoVC = nc.topViewController as? AddPhotoViewController else {
fatalError("AddPhotoViewController does not exist")
}
_ = addPhotoVC.selectedPhoto.subscribe(onNext: { (photo) in
self.photos.accept(self.photos.value + [photo])
})
}
private func configureObservables() {
if let collectionView = self.collectionView {
self.photos.bind(to: collectionView.rx.items(dataSource: self.dataSource))
self.photos.bind(to: collectionView.rx.items(cellIdentifier: "PhotoCollectionViewCell", cellType: PhotoCollectionViewCell.self)) { row, model, cell in
cell.photoImageView.image = UIImage(named: model.imageURL)
}.disposed(by: disposeBag)
}
}
}
The problem is that photos is the wrong type. The data source is expecting an element of [SectionOfPhoto], but photos has an element of [Photo].
However, changing/fixing the type of photos will break addPhotoVC.selectedPhoto because it's trying to add a single photo to an array of sections.
I'm trying to create a function that parse my JSON according to the ID at the end of the URL. For example: (https://alodjinha.herokuapp.com/produto?categoriaId=1). In this case, "categoriaId=1" will return me a "Games" category as a JSON filled with Games. It should changes depending on each category the user clicks on my UICollectionView categories. So, if the user clicks in "Movies" on my UICollectionView, I gotta change the url to id 2 (for example) https://alodjinha.herokuapp.com/produto?categoriaId=2 then I'll get the JSON filled with Movies and so on. However, It's not working what I'm doing wrong?
That's how I'm trying to get the category ID:
func getCategoriaPorID(IdCategoria:Int, completion:#escaping ([CategoriaIDItems])->Void) {
let url = URL(string: "https://alodjinha.herokuapp.com/produto?categoriaId=\(IdCategoria)")
let session = URLSession.shared
let request = URLRequest(url: url!)
let dataTask = session.dataTask(with: request) { (data, response, error) in
guard let unwrappedData = data else { print("Error data"); return}
do {
let jsonTop10 = try JSONDecoder().decode(CategoriaIDItemsData.self, from: unwrappedData)
completion(jsonTop10.data)
}catch{
print("Could no get API data")
}
}
dataTask.resume()
}
Models:
import Foundation
//Categorias
struct Contents : Decodable {
let data : [Content]
}
struct Content : Decodable {
let id : Int
let descricao : String
let urlImagem : String
}
//Banner
struct BannerData : Decodable {
let data : [Banner]
}
struct Banner : Decodable {
let id : Int
let urlImagem : String
let linkUrl : String
}
//Top10
struct Top10Data:Decodable {
let data: [Top10]
}
struct Top10:Decodable {
let id : Int
let nome : String
let urlImagem : String
let descricao : String
let precoDe : Int
}
struct CategoriaIDItemsData:Decodable {
let data : [CategoriaIDItems]
}
struct CategoriaIDItems:Decodable {
let id : Int
let nome : String
let urlImagem : String
let descricao : String
let precoDe : Int
}
Well, after that I proceeded to the main file (ViewController) where contains all my tables, such as, UITableView and also UICollectionview (Where all the categories are located).
import UIKit
class ViewController: UIViewController, UICollectionViewDataSource, UITableViewDataSource, UITableViewDelegate, UICollectionViewDelegate {
#IBOutlet weak var tableViewTopSell: UITableView!
#IBOutlet var collectionView: UICollectionView!
#IBOutlet weak var collectionViewBanner: UICollectionView!
var dataSource: [Content] = [Content]()
var dataBanner: [Banner] = [Banner]()
var dataTopSold: [Top10] = [Top10]()
var dataCategoriaID: [CategoriaIDItems] = [CategoriaIDItems]()
override func viewDidLoad() {
super.viewDidLoad()
//Delegate TableView
self.tableViewTopSell.delegate = self
//SetupNavBarCustom
self.navigationController?.navigationBar.CustomNavigationBar()
let logo = UIImage(named: "tag.png")
let imageView = UIImageView(image:logo)
self.navigationItem.titleView = imageView
//CallAPIData
getTopSold { (data) in
DispatchQueue.main.async {
self.dataTopSold = data
self.tableViewTopSell.reloadData()
}
}
getBanner { (data) in
DispatchQueue.main.async {
self.dataBanner = data
self.collectionViewBanner.reloadData()
}
}
getAudiobooksAPI { (data) in
DispatchQueue.main.async {
self.dataSource = data
self.collectionView.reloadData()
}
}
}
//CollectionView
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if (collectionView == self.collectionView) {
return self.dataSource.count
}else{
return self.dataBanner.count
}}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if (collectionView == self.collectionView) {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "collectionViewCell", for: indexPath) as! CollectionViewCell
let content = self.dataSource[indexPath.item]
cell.bookLabel.text = content.descricao
cell.bookImage.setImage(url: content.urlImagem, placeholder: "")
return cell
}else if (collectionView == self.collectionViewBanner) {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "collectionViewCellBanner", for: indexPath) as! CollectionViewCell
let content = self.dataBanner[indexPath.item]
cell.bannerImage.setImage(url: content.urlImagem, placeholder: "")
return cell
}
return UICollectionViewCell()
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
var indexPathId: Int
if (collectionView == self.collectionView) {
let content = self.dataSource[indexPath.item]
indexPathId = content.id
}else if (collectionView == self.collectionViewBanner) {
let content = self.dataBanner[indexPath.item]
indexPathId = content.id
}
getCategoriaPorID(IdCategoria: indexPathId) { (data) in
self.dataCategoriaID = data
self.performSegue(withIdentifier: "segueCategorias", sender:self.dataCategoriaID)
print(self.dataCategoriaID)
}
}
//TableView
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.dataTopSold.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "topSoldCell", for: indexPath) as! TableViewCell
let content = self.dataTopSold[indexPath.row]
cell.labelNomeTopSell.text = content.nome
cell.imageViewTopSell.setImage(url: content.urlImagem, placeholder: "")
cell.labelPrecoDe.text = "R$ \(content.precoDe)"
//Colocar strike em cima do Preco Antigo
let oldPrice = "R$ \(content.precoDe)"
let promotionString = oldPrice + ""
let attributedStr = NSMutableAttributedString(string: promotionString)
let crossAttr = [NSAttributedStringKey.strikethroughStyle: NSUnderlineStyle.styleSingle.rawValue]
attributedStr.addAttributes(crossAttr, range: NSMakeRange(0, oldPrice.count))
cell.labelPrecoDe.attributedText = attributedStr
//
cell.labelPrecoPor.text = "R$ 119.99"
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "segueId", sender:self.dataTopSold[indexPath.row])
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "segueId" {
let des = segue.destination as? TelaDetalheProdutos
//.item possui uma propriedade instanciada na TelaDetalheProdutos
des?.item = (sender as? Top10)
//Segue para CollectionView Categorias
} else if segue.identifier == "segueCategorias" {
let desc = segue.destination as? TelaCategorias
desc?.item = (sender as? CategoriaIDItems)
}
}
}
//Cast UIImage Extension
extension UIImageView{
func setImage(url : String, placeholder: String, callback : (() -> Void)? = nil){
self.image = UIImage(named: "no-photo")
URLSession.shared.dataTask(with: NSURL(string: url)! as URL, completionHandler: { (data, response, error) -> Void in
guard error == nil else{
return
}
DispatchQueue.main.async(execute: { () -> Void in
let image = UIImage(data: data!)
self.image = image
if let callback = callback{
callback()
}
})
}).resume()
}
}
Screen that will receive the data:
import UIKit
class TelaCategorias: UIViewController, UITableViewDataSource, UITableViewDelegate {
//Class Instanciated
var item:CategoriaIDItems?
var nome = String()
override func viewDidLoad() {
super.viewDidLoad()
????
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return ???
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "tableIDCategorias", for: indexPath) as! TelaCategoriasCell
????
return cell
}
}
App picture:
Main Screen
QUESTION SOLVED SUCCESSFULLY.
FIXED
I find myself stuck upon the implementation of a model for getting data from a firebase database.
I'm not sure what I've done so far is correct but as far as my knowledge of swift is concerned (I'm new to swift) I think I've followed the right path.
So I have a collection view which get the data from a firebase database.
The database structure is like so:
-SwimManager
--SwimmingPools
---SwimPoolName 1
-----Capacity: "2000"
-----PhotoUrl: "https//www.test"
---SwimPoolName 2
-----Capacity: "3000"
-----PhotoUrl: "https//www.test"
I'll show the code for the view controller, the model and the cell.
Here's my ViewController:
#IBOutlet weak var collectionView: UICollectionView!
var swimRef = Database.database().reference().child("SwimmingPools")
var swimmingPools = [SwimmingPool]()
verride func viewDidLoad() {
super.viewDidLoad()
collectionView.delegate = self
collectionView.dataSource = self
getSwimPoolInfo()
}
func getSwimPoolInfo() {
fishRef.observeSingleEvent(of: .value) { (snapshot) in
for child in snapshot.children {
let snap = child as! DataSnapshot
let swimNameFb = snap.key
let value = snap.value
let swim = Fish(swimName: swimNameFb, photoUrl: "")
self.swimmingPools.append(swim)
// Not sure how to add the picture
}
}
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "SwimCell", for: indexPath) as? SwimCell {
let swim: SwimminPool!
swim = swimmingPools[indexPath.row]
cell.configureCell(swim)
return cell
} else {
return UICollectionViewCell()
Model:
class SwimmingPool {
private var _swimName: String!
private var _photourl: String!
private var _capacity: String!
var swimName: String {
if _swimName == nil {
_swimName = ""
}
return _swimName
}
...............
init(swimName: String, photoUrl: String) {
self._SwimName = swimName
self._photourl = photoUrl
}
func getData() {
//perform action the get the data from the single swimmingPool (e.g. swimPoolName 1)
}
}
And finally, here's the cell:
class SwimCell: UICollectionViewCell {
#IBOutlet weak var swimThumb: UIImageView!
#IBOutlet weak var swimNameLbl: UILabel!
var swim: SwimmingPool!
func configureCell(_ swim: SwimmingPool) {
self.swim = swim
swimNameLbl.text = self.swim.swimName.capitalized
var url = URL(string: self.swim.photoUrl)
if url == nil {
url = URL(string: "")
}
swimThumb.sd_setImage(with: url)
}
}
In the Viewcontroller the func getSwimPooInfo is triggered after viewDidLoad and so the array swimminPools is empty... Honestly it seems I cannot figure where my mistake is....
Thx!
I'm a beginner using ReactiveCocoa with Swift for the first time. I'm building an app showing a list of movies and I'm using the MVVM pattern. My ViewModel looks like this:
class HomeViewModel {
let title:MutableProperty<String> = MutableProperty("")
let description:MutableProperty<String> = MutableProperty("")
var image:MutableProperty<UIImage?> = MutableProperty(nil)
private var movie:Movie
init (withMovie movie:Movie) {
self.movie = movie
title.value = movie.headline
description.value = movie.description
Alamofire.request(.GET, movie.pictureURL)
.responseImage { response in
if let image = response.result.value {
print("image downloaded: \(image)")
self.image.value = image
}
}
}
}
and I would like to configure my cells in the UITableView like this:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("MovieCell", forIndexPath: indexPath) as! MovieCell
let movie:Movie = movieList[indexPath.row]
let vm = HomeViewModel(withMovie: movie)
// fill cell with data
vm.title.producer.startWithNext { (newValue) in
cell.titleLabel.text = newValue
}
vm.description.producer.startWithNext { (newValue) in
cell.descriptioLabel.text = newValue
}
vm.image.producer.startWithNext { (newValue) in
if let newValue = newValue {
cell.imageView?.image = newValue as UIImage
}
}
return cell
}
Is this the right approach for Reactive Cocoa? Do I need to declare Title and description as Mutable or just image (being the only one changing). I think I could use binding but I'm not sure how to proceed.
to do this using Reactive Cocoa + MVVM patterns i would first move all the logic to configure the cell from its viewmodel into the cell class itself. and then remove the MutableProperties from the viewModel (they aren't actually mutable and we dont need those signals). and for the image expose a signal producer that will perform the network request to fetch the image when start() is called, rather than implicitly fetching it when init is called on the ViewModel, giving us something like
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("MovieCell", forIndexPath: indexPath) as! MovieCell
cell.viewModel = self.viewModelForIndexPath(indexPath)
return cell
}
private func viewModelForIndexPath(indexPath: NSIndexPath) -> MovieCellViewModel {
let movie: Movie = movieList[indexPath.row]
return HomeViewModel(movie: movie)
}
and then
class MovieCell: UITableViewCell
#IBOutlet weak var titleLabel: UILabel
#IBOutlet weak var descriptionLabel: UILabel
#IBOutlet weak var imageView: UIImageView
var viewModel: MovieCellViewModel {
didSet {
self.configureFromViewModel()
}
}
private func configureFromViewModel() {
self.titleLabel.text = viewModel.title
self.descriptionLabel.text = viewModel.description
viewModel.fetchImageSignal()
.takeUntil(self.prepareForReuseSignal()) //stop fetching if cell gets reused
.startWithNext { [weak self] image in
self?.imageView.image = image
}
}
//this could also go in a UITableViewCell extension if you want to use it other places
private func prepareForReuseSignal() -> Signal<(), NoError> {
return Signal { observer in
self.rac_prepareForReuseSignal // reactivecocoa builtin function
.toSignalProducer() // obj-c RACSignal -> swift SignalProducer
.map { _ in () } // AnyObject? -> Void
.flatMapError { _ in .empty } // NSError -> NoError
.start(observer)
}
}
}
and in the ViewModel
struct HomeViewModel {
private var movie: Movie
var title: String {
return movie.headline
}
var description: String {
return movie.description
}
func fetchImageSignal() -> SignalProducer<UIImage, NSError> {
return SignalProducer { observer, disposable in
Alamofire.request(.GET, movie.pictureURL)
.responseImage { response in
if let image = response.result.value {
print("image downloaded: \(image)")
observer.sendNext(image) //send the fetched image on the signal
observer.sendCompleted()
} else {
observer.sendFailed( NSError(domain: "", code: 0, userInfo: .None)) //send your error
}
}
}
}