I have a tableview loading data from firebase database. When i open my app the data does not populate. it crashes and throws the error index out of range. I am fairly new to xcode and would appreciate the help. i have been given recommendations on how to fix my code with regards to having multiple arrays but everyone that advices forget i'm new and does realize that i am pulling imageurls into my tableview too and dont know how to adopt their recommendations into my code. if i could get help getting passed this error that would be awesome.
import UIKit
import FirebaseAuth
import FirebaseDatabase
class EventsViewController: UIViewController,UITableViewDelegate,UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
var eventsRef: DatabaseReference?
var eventsDatabaseHandle:DatabaseHandle?
var eventsTitles = [String]()
var eventTimestamps = [String]()
var eventsLocations = [String]()
var eventsImages = [UIImage]()
#IBOutlet weak var addEventsButton: UIBarButtonItem!
override func viewDidLoad() {
super.viewDidLoad()
adminAuth()
eventsRef = Database.database().reference()
tableView.reloadData()
tableView.transform = CGAffineTransform(rotationAngle: -CGFloat.pi)
tableView.delegate = self
tableView.dataSource = self
eventsDatabaseHandle = eventsRef?.child("Church Events").observe(.childAdded, with: { (snaphot) in
let eventPost = snaphot.value as! [String: Any]
self.eventTimestamps.append(eventPost["eventdate"] as! String)
self.eventsTitles.append(eventPost["eventtitle"] as! String)
self.eventsLocations.append(eventPost["eventlocation"] as! String)
let task = URLSession.shared.dataTask(with: URL(string: eventPost["ImageUrl"] as! String)!) {(data, response, error) in
if let image: UIImage = UIImage(data: data!) {
self.eventsImages.append(image)
}
}
task.resume()
self.tableView.reloadData()
})
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return eventsTitles.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "events") as! EventsTableViewCell
// let image = eventsImages[indexPath.row]
cell.flyerImages.image? = eventsImages[indexPath.row] **<- index out of range**
cell.eventTitle.text! = eventsTitles[indexPath.row]
cell.eventDate.text! = eventTimestamps[indexPath.row]
cell.eventLocation.text! = eventsLocations[indexPath.row]
cell.transform = CGAffineTransform(rotationAngle: CGFloat.pi)
return cell
}
func adminAuth() {
if (Auth.auth().currentUser!.displayName != "Neil Leon") {
self.addEventsButton.tintColor = UIColor.clear
self.addEventsButton.isEnabled = false
}
else{
self.addEventsButton.isEnabled = true
}
}
}``````
At the moment reloadData is called the image array is empty and causes the crash
Use a custom struct and reload the table view after the image data has been received
struct Event {
let title, timestamp, location : String
var image : UIImage?
}
var events = [Event]()
...
eventsDatabaseHandle = eventsRef?.child("Church Events").observe(.childAdded, with: { (snaphot) in
let eventPost = snaphot.value as! [String: Any]
let event = Event(title: eventPost["eventtitle"] as! String,
timestamp: eventPost["eventdate"] as! String,
location: eventPost["eventlocation"] as! String,
image: nil)
let task = URLSession.shared.dataTask(with: URL(string: eventPost["ImageUrl"] as! String)!) { data, _, error in
if let error = error {
print(error)
} else {
event.image = UIImage(data: data!)
DispatchQueue.main.async {
self.events.append(event)
self.tableView.reloadData()
}
}
}
task.resume()
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return events.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "events", for: indexPath) as! EventsTableViewCell
let event = events[indexPath.row]
cell.flyerImages.image = event.image
cell.eventTitle.text = event.title
cell.eventDate.text = event.timestamp
cell.eventLocation.text = event.location
cell.transform = CGAffineTransform(rotationAngle: CGFloat.pi)
return cell
}
Related
I get this error when I run the application.
Fatal error: Index out of range: file Swift/ContiguousArrayBuffer.swift, line 444
I pull data from firebase and upload it to MapKit in UITableViewCell. It was working on my friend's computer but when I run it again on my own computer, I got this error. How can I solve this problem?
ViewController:
import UIKit
import Firebase
import SDWebImage
import MapKit
import CoreLocation
class anasayfaViewController: UIViewController, UITableViewDelegate, UITableViewDataSource,CLLocationManagerDelegate, MKMapViewDelegate {
#IBOutlet weak var tableView: UITableView!
var UserNameArray = [String]()
var UserCommentArray = [String]()
var UserImageArray = [String]()
var DocumentIDarray = [String]()
var UserLocationArray = [GeoPoint]()
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return UserNameArray.count
}
/*
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
*/
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! HomeCell
let username = UserNameArray[indexPath.row]
let comment = UserCommentArray[indexPath.row]
let imageURL = UserImageArray[indexPath.row]
let location = UserLocationArray[indexPath.row]. ------ (Error Line )
cell.prepare(username: username, imageURL: imageURL, comment: comment, location: location)
return cell
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
//tableView.estimatedRowHeight = 100
getdataFromFirestore()
}
func getdataFromFirestore(){
let firestoreDatabase = Firestore.firestore()
firestoreDatabase.collection("Posts").order(by: "Date", descending: true).addSnapshotListener { [weak self] (snapshot, error) in
if let error = error {
print(error.localizedDescription)
return
}
if snapshot?.isEmpty != true {
self?.UserCommentArray.removeAll(keepingCapacity: false)
self?.UserNameArray.removeAll(keepingCapacity: false)
self?.UserImageArray.removeAll(keepingCapacity: false)
self?.documentIDarray.removeAll(keepingCapacity: false)
self?.UserLocationArray.removeAll(keepingCapacity: false)
for document in snapshot!.documents {
let documentID = document.documentID
self?.documentIDarray.append(documentID)
if let sharedBy = document.get("SharedBy") as? String {
self?.UserNameArray.append(sharedBy)
}
if let sharedComment = document.get("SharedComment") as? String {
self?.UserCommentArray.append(sharedComment)
}
if let sharedUrl = document.get("imageUrl") as? String {
self?.UserImageArray.append(sharedUrl)
}
if let sharedLocation = document.get("Location") as? GeoPoint {
self?.UserLocationArray.append(sharedLocation)
}
}
self?.tableView.reloadData()
}
}}
}
I have a tableview loading data from firebase database. When I open my app the data does not populate. when I create a new post I can see the tableview cells modifying like changed were made but the post doesn't populate. I can't figure it out.
import UIKit
import FirebaseAuth
import FirebaseDatabase
class EventsViewController: UIViewController,UITableViewDelegate,UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
var eventsRef: DatabaseReference?
var eventsDatabaseHandle:DatabaseHandle?
var eventsTitles = [String]()
var eventTimestamps = [String]()
var eventsLocations = [String]()
var eventsImages = [UIImage]()
#IBOutlet weak var addEventsButton: UIBarButtonItem!
override func viewDidLoad() {
super.viewDidLoad()
adminAuth()
eventsRef = Database.database().reference()
tableView.reloadData()
tableView.transform = CGAffineTransform(rotationAngle: -CGFloat.pi)
tableView.delegate = self
tableView.dataSource = self
eventsDatabaseHandle = eventsRef?.child("Church Events").observe(.childAdded, with: { (snaphot) in
let eventPost = snaphot.value as! [String: Any]
self.eventTimestamps.append(eventPost["eventdate"] as! String)
self.eventsTitles.append(eventPost["eventtitle"] as! String)
self.eventsLocations.append(eventPost["eventlocation"] as! String)
let task = URLSession.shared.dataTask(with: URL(string: eventPost["ImageUrl"] as! String)!) {(data, response, error) in
if let image: UIImage = UIImage(data: data!) {
self.eventsImages.append(image)
}
}
self.tableView.reloadData()
task.resume()
})
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return eventsImages.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "events") as! EventsTableViewCell
let image = eventsImages[indexPath.row]
cell.flyerImages.image! = image
cell.eventTitle.text! = eventsTitles[indexPath.row]
cell.eventDate.text! = eventTimestamps[indexPath.row]
cell.eventLocation.text! = eventsLocations[indexPath.row]
cell.transform = CGAffineTransform(rotationAngle: CGFloat.pi)
tableView.reloadData()
return cell
}
func adminAuth() {
if (Auth.auth().currentUser!.displayName != "Neil Leon") {
self.addEventsButton.tintColor = UIColor.clear
self.addEventsButton.isEnabled = false
}
else{
self.addEventsButton.isEnabled = true
}
}
}
image of empty tableview
]
So the code below is not tested as I don't have firebase setup currently.
However, observing childAdded... the documentation says it will pass all of the current records in the database at first and will then just post new additions. So all you need to do is loop through them, setup your tableView data source and reload the table.
Rather than use multiple arrays for values I've created an array of ChurchEvent objects instead.
struct ChurchEvents {
let title: String
let location: String?
let date: Date?
let imageUrlString: String?
init(dict: [String: Any]) {
self.title = dict["title"] as String
self.location = dict["location"] as? String
// etc
}
}
var events = [ChurchEvents]()
eventsDatabaseHandle = eventsRef?.child("Church Events").observe(.childAdded, with: { snapshot in
let list = snapshot.value as? [[String : AnyObject]]
let newEvents = list.map { ChurchEvent(dict: $0) }
events.append(newEvents)
tableView.reloadData()
}
Other improvements you could make:
class EventsTableViewCell: UICollectionViewCell {
func configure(with event: ChurchEvent {
eventDate.text = event.date
eventTitle.text = event.title
eventLocation.text = event.location
// etc
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "events") as! EventsTableViewCell
let event = events[indexPath.row]
cell.configure(with: event)
return cell
}
Essentially I have am using JSON data to create an array and form a tableview.
I would like the table cells to be grouped by one of the fields from the JSON array.
This is what the JSON data looks like:
[{"customer":"Customer1","number":"122039120},{"customer":"Customer2","number":"213121423"}]
Each number needs to be grouped by each customer.
How can this be done?
This is how I've implemented the JSON data using the table:
CustomerViewController.swift
import UIKit
class CustomerViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, FeedCustomerProtocol {
var feedItems: NSArray = NSArray()
var selectedStock : StockCustomer = StockCustomer()
let tableView = UITableView()
#IBOutlet weak var customerItemsTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
//set delegates and initialize FeedModel
self.tableView.allowsMultipleSelection = true
self.tableView.allowsMultipleSelectionDuringEditing = true
self.customerItemsTableView.delegate = self
self.customerItemsTableView.dataSource = self
let feedCustomer = FeedCustomer()
feedCustomer.delegate = self
feedCustomer.downloadItems()
}
}
func itemsDownloaded(items: NSArray) {
feedItems = items
self.customerItemsTableView.reloadData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// Return the number of feed items
print("item feed loaded")
return feedItems.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Retrieve cell
let cell = tableView.dequeueReusableCell(withIdentifier: "customerGoods", for: indexPath) as? CheckableTableViewCell
let cellIdentifier: String = "customerGoods"
let myCell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier)!
// Get the stock to be shown
let item: StockCustomer = feedItems[indexPath.row] as! StockCustomer
// Configure our cell title made up of name and price
let titleStr = [item.number].compactMap { $0 }.joined(separator: " - ")
return myCell
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
tableView.cellForRow(at: indexPath)?.accessoryType = .none
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.cellForRow(at: indexPath)?.accessoryType = .checkmark
let cellIdentifier: String = "customerGoods"
let myCell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier)!
myCell.textLabel?.textAlignment = .left
}
}
FeedCustomer.swift:
import Foundation
protocol FeedCustomerProtocol: class {
func itemsDownloaded(items: NSArray)
}
class FeedCustomer: NSObject, URLSessionDataDelegate {
weak var delegate: FeedCustomerProtocol!
let urlPath = "https://www.example.com/example/test.php"
func downloadItems() {
let url: URL = URL(string: urlPath)!
let defaultSession = Foundation.URLSession(configuration: URLSessionConfiguration.default)
let task = defaultSession.dataTask(with: url) { (data, response, error) in
if error != nil {
print("Error")
}else {
print("stocks downloaded")
self.parseJSON(data!)
}
}
task.resume()
}
func parseJSON(_ data:Data) {
var jsonResult = NSArray()
do{
jsonResult = try JSONSerialization.jsonObject(with: data, options:JSONSerialization.ReadingOptions.allowFragments) as! NSArray
} catch let error as NSError {
print(error)
}
var jsonElement = NSDictionary()
let stocks = NSMutableArray()
for i in 0 ..< jsonResult.count
{
jsonElement = jsonResult[i] as! NSDictionary
let stock = StockCustomer()
//the following insures none of the JsonElement values are nil through optional binding
if let number = jsonElement[“number”] as? String,
let customer = jsonElement["customer"] as? String,
{
stock.customer = customer
stock.number = number
}
stocks.add(stock)
}
DispatchQueue.main.async(execute: { () -> Void in
self.delegate.itemsDownloaded(items: stocks)
})
}
}
StockCustomer.swift:
import UIKit
class StockCustomer: NSObject {
//properties of a stock
var customer: String?
var number: String?
//empty constructor
override init()
{
}
//construct with #name and #price parameters
init(customer: String) {
self.customer = customer
}
override var description: String {
return "Number: \(String(describing: number)), customer: \(String(describing: customer))"
}
}
You can achieve this by making an array of array. So something like this
[[{"customer": "customer1", "number": "123"}, {"customer": "customer1", "number": "456"}], [{"customer": "customer2", "number": "678"}, {"customer": "customer2", "number": "890"}]]
This is not the only data structure you can use to group. Another possibility is:
{"customer1": [{"customer": "customer1", "number": "123"}, {"customer": "customer1", "number": "456"}], "customer2": [{"customer": "customer2", "number": "678"}, {"customer": "customer2", "number": "890"}]}
Then you can use UITableView sections to group by customers. Section count would be the number of inside arrays and each section would contain as many rows as there are numbers in that inside array.
You can group a sequence based on a particular key using one of the Dictionary initializer,
init(grouping:by:)
The above method init will group the given sequence based on the key you'll provide in its closure.
Also, for parsing such kind of JSON, you can easily use Codable instead of manually doing all the work.
So, for that first make StockCustomer conform to Codable protocol.
class StockCustomer: Codable {
var customer: String?
var number: String?
}
Next you can parse the array like:
func parseJSON(data: Data) {
do {
let items = try JSONDecoder().decode([StockCustomer].self, from: data)
//Grouping the data based on customer
let groupedDict = Dictionary(grouping: items) { $0.customer } //groupedDict is of type - [String? : [StockCustomer]]
self.feedItems = Array(groupedDict.values)
} catch {
print(error.localizedDescription)
}
}
Read about init(grouping:by:) in detail here: https://developer.apple.com/documentation/swift/dictionary/3127163-init
Make the feedItems object in CustomerViewController of type [[StockCustomer]]
Now, you can implement UITableViewDataSource methods as:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.feedItems.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "customerGoods", for: indexPath) as! CheckableTableViewCell
let items = self.feedItems[indexPath.row]
cell.textLabel?.text = items.compactMap({$0.number}).joined(separator: " - ")
//Configure the cell as per your requirement
return cell
}
Try implementing the approach with all the bits and pieces and let me know in case you face any issues.
I'm trying to display JSON value using Struct into table view in Swift 3.0 .
I managed to display JSON value using struct into UIPickerView and it works fine. But somehow when I use the same way in table view it does not work.
Can someone help me to update my code below in order to make it work in UITableView ? Appreciate if someone can help. Thanks.
list.php output
[
{"id":"101", "name":"name1"},
{"id":"102", "name":"name2"}
]
ViewController.swift
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource{
#IBOutlet var tableview: UITableView!
#IBOutlet var deptLbl: UILabel!
#IBOutlet var selectBtn: UIButton!
#IBOutlet var displayIDLbl: UILabel!
#IBOutlet var displayNameLbl: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
deptLbl.text = "ITD"
self.tableview.isHidden = true
getList()
}
var persons = [Person]()
struct Person {
var id: String
var name: String
init?(dict: [String:Any]) {
guard let id = dict["id"] as? String, let name = dict["name"] as? String else {
return nil
}
self.id = id
self.name = name
}
}
func getList() {
var request = URLRequest(url: URL(string: "http://localhost/list.php")!)
request.httpMethod = "POST"
let postString = "dept=\(deptLbl.text!)"
request.httpBody = postString.data(using: .utf8)
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
guard let data = data, error == nil else {
return
}
if let array = (try? JSONSerialization.jsonObject(with: data, options: [])) as? [[String:Any]] {
self.persons = array.flatMap(Person.init)
DispatchQueue.main.async {
self.tableview.reloadData()
}
}
}
task.resume()
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self .persons.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "listcell", for: indexPath)
let data = persons[indexPath.row]
cell.textLabel?.text = data.id
cell.textLabel?.text = data.name
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.displayIDLbl.text = self.persons[indexPath.row].id
self.displayNameLbl.text = self.persons[indexPath.row].name
self.tableview.isHidden = true
}
#IBAction func pressedBtn(_ sender: Any) {
self.tableview.isHidden = !self.tableview.isHidden
}
}
I'm creating an e-commerce app with (Moltin.com) SDK, I set every thing well as it shown in the documentation but now I need to load multi images of single product in table view with custom cell, I set the shown code below and all I can get is a single image my app ignore load the other images view controller code is
class vc: UIViewController , UITableViewDelegate, UITableViewDataSource {
var productDict:NSDictionary?
#IBOutlet weak var tableview: UITableView!
fileprivate let MY_CELL_REUSE_IDENTIFIER = "MyCell"
fileprivate var productImages:NSArray?
override func viewDidLoad() {
super.viewDidLoad()
tableview.delegate = self
tableview.dataSource = self
Moltin.sharedInstance().product.listing(withParameters: productDict!.value(forKeyPath: "url.https") as! [String : Any]!, success: { (response) -> Void in
self.productImages = response?["result"] as? NSArray
self.tableview?.reloadData()
}) { (response, error) -> Void in
print("Something went wrong...")
}
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if productImages != nil {
return productImages!.count
}
return 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: MY_CELL_REUSE_IDENTIFIER, for: indexPath) as! MyCell
let row = (indexPath as NSIndexPath).row
let collectionDictionary = productImages?.object(at: row) as! NSDictionary
cell.setCollectionDictionary(collectionDictionary)
return cell
}
and my custom cell code is
class MyCell: UITableViewCell {
#IBOutlet weak var myImage: UIImageView!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
func setCollectionDictionary(_ dict: NSDictionary) {
// Set up the cell based on the values of the dictionary that we've been passed
// Extract image URL and set that too...
var imageUrl = ""
if let images = dict.value(forKey: "images") as? NSArray {
if (images.firstObject != nil) {
imageUrl = (images.firstObject as! NSDictionary).value(forKeyPath: "url.https") as! String
}
}
myImage?.sd_setImage(with: URL(string: imageUrl))
}
Can anyone show me where is the issue that doesn't let me get all the images of my product?
I'm using SWIFT 3, with XCode
In the code below you are always getting one URL from images array (firstObject).
if let images = dict.value(forKey: "images") as? NSArray {
if (images.firstObject != nil) {
imageUrl = (images.firstObject as! NSDictionary).value(forKeyPath: "url.https") as! String
}
}
myImage?.sd_setImage(with: URL(string: imageUrl))
If I understand correctly you should get every image in images array by the indexPath.row of your tableView.
For example add new parameter to method like this:
func setCollection(with dict: NSDictionary, and index: Int) {
// Set up the cell based on the values of the dictionary that we've been passed
// Extract image URL and set that too...
var imageUrlString = ""
if let images = dict.value(forKey: "images") as? Array<NSDictionary>, images.count >= index {
guard let lImageUrlString = images[index]["url.https"] else { return }
imageUrlString = lImageUrlString
}
guard let imageURL = URL(string: imageUrl) else { return }
myImage?.sd_setImage(with: imageURL)
}
Than when call this method in cellForRow just add indexPath.row to the second param.
But if you want show multiple images in one cell you should add more imageViews to the custom cell or use UICollectionView.
Just ping me if I don't understand you clear.