How do I display, update and delete my cells? - ios

I am taking a class to create a client list iOS app and connect it to Realm, I am not a great coder and my online school teaches poorly, so Youtube is my teacher most of the time. I am working off a Realm app video but am unsure how to do things past where they end. The app so far is a tableview, when I select the plus button in the top right corner, it opens a field to enter the client's name, address, city, state and zip code. when I click save it then stores the info to Realm and displays a Subtitle cell with the client's name and city. I would like to be able to delete the cell by swiping and also be able to view the full contents of the cell and update the cell. If anyone can offer any assistance I would be very grateful. Please see my ViewController below:
import UIKit
import RealmSwift
class ContactItem: Object {
#objc dynamic var name = ""
#objc dynamic var addressOne = ""
#objc dynamic var addressTwo = ""
#objc dynamic var city = ""
#objc dynamic var state = ""
#objc dynamic var zipCode = ""
#objc dynamic var _id: ObjectId = ObjectId.generate()
convenience init(name: String, addressOne: String, addressTwo: String, city: String, state: String, zipCode: String, _id: String) {
self.init()
self.name = name
self.addressOne = addressOne
self.addressTwo = addressTwo
self.city = city
self.state = state
self.zipCode = zipCode
}
override static func primaryKey() -> String? {
return "_id"
}
}
class ViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
let mike = ContactItem(name: "Mike", addressOne: "234 Maple Lane", addressTwo: "", city: "Ankeny", state: "Ohio", zipCode: "45665", _id: "0")
var people = try! Realm().objects(ContactItem.self).sorted(byKeyPath: "name", ascending: true)
var realm = try! Realm()
var db: OpaquePointer?
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
if realm.isEmpty {
try! realm.write {
realm.add(mike)
}
}
let path = realm.configuration.fileURL?.path
print("Path: \(String(describing: path))")
}
#IBAction func addButtonTapped(_ sender: Any) {
let alertController = UIAlertController(title: "Add Client", message: "", preferredStyle: .alert)
//when the user clicks the add button, present them with a dialog to enter the task name.
alertController.addAction(UIAlertAction(title: "Save", style: .default, handler: {
alert -> Void in
let nameField = alertController.textFields![0] as UITextField
let addOneField = alertController.textFields![1] as UITextField
let addTwoField = alertController.textFields![2] as UITextField
let cityField = alertController.textFields![3] as UITextField
let stateField = alertController.textFields![4] as UITextField
let zipField = alertController.textFields![5] as UITextField
//Create a new Task with the text that the user entered.
let newPerson = ContactItem(name: nameField.text!, addressOne: addOneField.text!, addressTwo: addTwoField.text!, city: cityField.text!, state: stateField.text!, zipCode: zipField.text!, _id: "")
//Any writes to the Realm must occur in a write block
try! self.realm.write {
//Add the Task to the Realm. That's it!
self.realm.add(newPerson)
self.tableView.reloadData()
}
}))
alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
alertController.addTextField(configurationHandler: {
(nameField: UITextField!) -> Void in nameField.placeholder = "Enter Client Name"
})
alertController.addTextField(configurationHandler: {
(addOneField: UITextField!) -> Void in addOneField.placeholder = "Enter First Address Line"
})
alertController.addTextField(configurationHandler: {
(addTwoField: UITextField!) -> Void in addTwoField.placeholder = "Enter Second Address Line"
})
alertController.addTextField(configurationHandler: {
(cityField: UITextField!) -> Void in cityField.placeholder = "Enter City"
})
alertController.addTextField(configurationHandler: {
(stateField: UITextField!) -> Void in stateField.placeholder = "Enter State"
})
alertController.addTextField(configurationHandler: {
(zipField: UITextField!) -> Void in zipField.placeholder = "Enter Zip Code"
})
//Show dialog.
self.present(alertController, animated: true)
}
}
extension ViewController: UITableViewDelegate {
}
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return people.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = people[indexPath.row].name
cell.detailTextLabel?.text = people[indexPath.row].city
return cell
}
}
Any help would be greatly appreciated.
PS. There may be some garbage code in there, I was trying a lot of different things before so I might have accidentally left some confusing code. I apologize for the mess.

Related

Designating switch to record Xcode iOS

Hi all,
I am trying to build an app for my school project that I am building on Xcode for iOS. The app involves the user being able to save information about their bills. This data is string data and includes the potential for the user being able to save the name of a bill and when the bill was received. I want it that if the bill is related to a specific expenses, for that data to be saved to a specific record that holds data for that specific expense. For example, if the user received a bill for their car, they would enter the bill details and that data could be saved to one specific record that holds all car expense bill data. The way the user could specify where they want to data to go too is through switching on the relevant switch. I am not sure how I can get it that when the user turns on, for example car, that the data will save to that record. Could you please help?
I have included my code and the photo of my interface.
Thank you
class ViewControllerAdd: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate, UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return cars.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//ensure the cell identifier has been labelled "cell"
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
let cars = cars[indexPath.row];
let text = "\(cars.provider) \(cars.date)";
cell.textLabel?.text=text
return cell
}
override func viewDidLoad() {
super.viewDidLoad()
tblCar.delegate = self
tblCar.dataSource = self
tblCar.reloadData()
// Formatting for take picture button
imgView.backgroundColor = .secondarySystemBackground
btnLook.backgroundColor = .systemBlue
btnLook.setTitle("Take Picture", for: .normal)
btnLook.setTitleColor(.white, for: .normal)
// datepicker function from https://www.youtube.com/watch?v=fhRJ5HQjBIE
// This is how the date picker changes the date and date is saved
let datePicker = UIDatePicker()
datePicker.datePickerMode = .date
datePicker.addTarget(self, action: #selector(dateChange(datePicker:)), for: UIControl.Event.valueChanged)
datePicker.frame.size = CGSize(width: 0, height: 300)
datePicker.preferredDatePickerStyle = .wheels
txtDate.inputView = datePicker
txtDate.text = formateDate(date: Date())
// Getting keyboard to retract
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.view.endEditing(true)
}
//outlet
// Make segmented control action
#IBOutlet weak var txtBill: UITextField!
#IBOutlet weak var txtDate: UITextField!
#IBOutlet weak var btnLook: UIButton!
#IBOutlet weak var swiCar: UISwitch!
#IBOutlet weak var imgView: UIImageView!
#IBOutlet weak var tblCar: UITableView!
struct Car{
let provider: String
let date: String
// let bill: String
}
struct Went{
let provider: String
let date: String
// let bill: String
}
struct South{
let provider: String
let date: String
// let bill: String
}
struct Bruns{
let provider: String
let date: String
// let bill: String
}
struct Home{
let provider: String
let date: String
// let bill: String
}
struct Telephone{
let provider: String
let date: String
// let bill: String
}
var cars: [Car] = []
var went: [Went] = []
var south: [South] = []
var bruns: [Bruns] = []
var home: [Home] = []
var telephone: [Telephone] = []
//action
#IBAction func btnAdd(_ sender: Any) {
let newCar = Car(provider: txtBill.text!, date:txtDate.text!)
let newWent = Went(provider: txtBill.text!, date: txtDate.text!)
let newBruns = Bruns(provider: txtBill.text!, date: txtDate.text!)
let newSouth = South(provider: txtBill.text!, date: txtDate.text!)
let newHome = Home(provider: txtBill.text!, date: txtDate.text!)
let newTelephone = Telephone(provider: txtBill.text!, date: txtDate.text!)
cars.append(newCar)
went.append(newWent)
south.append(newSouth)
bruns.append(newBruns)
home.append(newHome)
telephone.append(newTelephone)
let imageData = imgView.image!.pngData()
let compressedImage = UIImage(data: imageData!)
UIImageWriteToSavedPhotosAlbum(compressedImage!, nil, nil, nil)
let alert = UIAlertController(title: "Saved", message: "Image Saved", preferredStyle: .alert)
let okAction = UIAlertAction(title: "Ok", style:.default, handler: nil)
alert.addAction(okAction)
self.present(alert, animated: true, completion:nil)
if swiCar.isOn{
cars.append(newCar) = tblCar
}
}
// Functionality
#IBAction func btnPhoto(_ sender: Any) {
// Checks if app can access camera
if UIImagePickerController.isSourceTypeAvailable(UIImagePickerController.SourceType.camera) {
// If camera can be accessed, it will set the class to the delegate and control the camera
let imagePicker = UIImagePickerController()
imagePicker.delegate = self
imagePicker.sourceType = UIImagePickerController.SourceType.camera
// Allows user to edit photo
imagePicker.allowsEditing = true
self.present(imagePicker, animated: true, completion: nil)
}
}
#objc func dateChange(datePicker: UIDatePicker) {
txtDate.text = formateDate(date: datePicker.date)
}
func formateDate(date: Date) -> String {
let formatter = DateFormatter()
formatter.dateFormat = "MMMM dd yyyy"
return formatter.string(from: date)
}
}
If you want to segregate expenses based on type of expenses, have enum of "expenseType" something like below.
enum ExpenseType{
case Car
case Telephone
case Grossary
case Misc
}
Have a single expense object in which mention "expenseType" as attribute. Based on user selection of switch add that expense to corresponding record.
In this case you should not allow user to select more than one switch.

The data from Firestore database is not displayed in the tableview

I'm using Swift and Firestore database to implement an app like Twitter.
I want to add sweet (it's like tweet) when button is clicked to the database. And then display it in the tableview.
The data is added to the database. But is not displayed in the tableview. So when I run an app I see empty tableview.
Please help!!
TableViewController file:
import UIKit
import FirebaseFirestore
import Firebase
class TableViewController: UITableViewController {
var db:Firestore!
var sweetArray = [Sweet]()
override func viewDidLoad() {
super.viewDidLoad()
db = Firestore.firestore()
loadData()
}
func loadData() {
db.collection("sweets").getDocuments() {
querySnapshot, error in
if let error = error {
print("Error loading documents to the db: \(error.localizedDescription)")
} else {
self.sweetArray = querySnapshot!.documents.flatMap({Sweet(dictionary: $0.data())})
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
}
#IBAction func composeSweet(_ sender: Any) {
let composeAlert = UIAlertController(title: "New Sweet", message: "Enter your name and message", preferredStyle: .alert)
composeAlert.addTextField { (textField:UITextField) in
textField.placeholder = "Your name"
}
composeAlert.addTextField { (textField:UITextField) in
textField.placeholder = "Your message"
}
composeAlert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
composeAlert.addAction(UIAlertAction(title: "Send", style: .default, handler: { (action:UIAlertAction) in
if let name = composeAlert.textFields?.first?.text, let content = composeAlert.textFields?.last?.text {
let newSweet = Sweet(name: name, content: content, timeStamp: Date())
var ref:DocumentReference? = nil
ref = self.db.collection("sweets").addDocument(data: newSweet.dictionary) {
error in
if let error = error {
print("Error adding document: \(error.localizedDescription)")
} else {
print("Document added with ID: \(ref!.documentID)")
}
}
}
}))
self.present(composeAlert, animated: true, completion: nil)
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return sweetArray.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
let sweet = sweetArray[indexPath.row]
cell.textLabel?.text = "\(sweet.name) : \(sweet.content)"
cell.detailTextLabel?.text = "\(sweet.timeStamp)"
return cell
}
}
Sweet file:
import Foundation
import FirebaseFirestore
protocol DocumentSerializable {
init?(dictionary:[String:Any])
}
struct Sweet {
var name: String
var content: String
var timeStamp: Date
var dictionary:[String:Any] {
return [
"name": name,
"content": content,
"timeStamp": timeStamp
]
}
}
extension Sweet:DocumentSerializable {
init?(dictionary: [String : Any]) {
guard let name = dictionary["name"] as? String,
let content = dictionary["content"] as? String,
let timeStamp = dictionary["timeStamp"] as? Date else {return nil}
self.init(name: name, content: content, timeStamp: timeStamp)
}
}
My storyboards:
My running app:
I can't provide a specific answer but I can explain how to find what the issue is.
While adding guard statements to protect your code is awesome, it can also lead to issues not being handled appropriately.
Take this piece of code from your question for example
guard let name = dictionary["name"] as? String,
let content = dictionary["content"] as? String,
let timeStamp = dictionary["timeStamp"] as? Date else {return nil}
As you can see if there's some issue with name, content or timestamp, the guard will catch it - however, returning nil means it silently fails with no indication of the problem.
Suppose for example, that a field name was accidentally called Name instead of name - well, that's going to fail but you'd never know it.
I suggest handling fields separately to catch specific problems. Like this
let name = dictionary["name"] as? String ?? "name field not found"
let name = dictionary["content"] as? String ?? "content field not found"
let name = dictionary["timesStamp"] as? Date ?? "timestamp field not found"
This is called nil coalescing and will substitute a default value in case of nil. By then examining the incoming data, you can find the document that caused the issue. You could also do this
guard let name = dictionary["name"] as? String else { //handle the error }
in either case, you then have more data about the nature of the failure.
Seems you have data in querySnapshot but empty in sweetArray which means only one this your are parsing and mapping the data received into structs incorrectly. Modify this line to fix your issue:
self.sweetArray = querySnapshot!.documents.flatMap({Sweet(dictionary: $0.data())})
private var refernceCollection: CollectionReference!
database = Firestore.firestore()
refernceCollection = Firestore.firestore().collection(kMessages)
func fetchData() {
refernceCollection.addSnapshotListener{ snapshots, error in
if error != nil {
print("error --->>")
} else {
guard let snap = snapshots else { return }
var arrUser:[MDLMessages] = []
for documet in snap.documents {
let data = documet.data()
let message = data["message"] as? String ?? "This message was deleted"
let time = data["time"] as? Date ?? Date.now
let documentId = documet.documentID
let userId = data["userId"] as? String ?? ""
let details = MDLMessages(message: message, time: time, documentId: documentId, userId: userId)
arrUser.append(details)
}
DispatchQueue.main.async {
self.arrMessages = arrUser
self.tblChatDetails.reloadData()
}
}
}
}

Download image to tableview from Firebase

class ArtistModel {
var id: String?
var name: String?
var genre: String?
var img: String?
init(id: String?, name: String?, genre: String?, img: String?){
self.id = id
self.name = name
self.genre = genre
self.img = img
}
}
and this my tableviewCell
class addArtistTableViewCell: UITableViewCell {
#IBOutlet weak var lblName: UILabel!
#IBOutlet weak var lblGenre: UILabel!
#IBOutlet weak var img: UIImageView!
and this my viewController
import UIKit
import Firebase
import FirebaseDatabase
class addArtistViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var refArtists: FIRDatabaseReference!
#IBOutlet weak var textFieldName: UITextField!
#IBOutlet weak var textFieldGenre: UITextField!
#IBOutlet weak var img: UIImageView!
#IBOutlet weak var labelMessage: UILabel!
#IBOutlet weak var tableViewArtists: UITableView!
//list to store all the artist
var artistList = [ArtistModel]()
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
//getting the selected artist
let artist = artistList[indexPath.row]
//building an alert
let alertController = UIAlertController(title: artist.name, message: "Give new values to update ", preferredStyle: .alert)
//the confirm action taking the inputs
let confirmAction = UIAlertAction(title: "Enter", style: .default) { (_) in
//getting artist id
let id = artist.id
//getting new values
let name = alertController.textFields?[0].text
let genre = alertController.textFields?[1].text
//calling the update method to update artist
self.updateArtist(id: id!, name: name!, genre: genre!)
}
//the cancel action doing nothing
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) { (_) in }
//adding two textfields to alert
alertController.addTextField { (textField) in
textField.text = artist.name
}
alertController.addTextField { (textField) in
textField.text = artist.genre
}
//adding action
alertController.addAction(confirmAction)
alertController.addAction(cancelAction)
//presenting dialog
present(alertController, animated: true, completion: nil)
}
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
return artistList.count
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
//creating a cell using the custom class
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! addArtistTableViewCell
//the artist object
let artist: ArtistModel
//getting the artist of selected position
artist = artistList[indexPath.row]
//adding values to labels
cell.lblName.text = artist.name
cell.lblGenre.text = artist.genre
//returning cell
return cell
}
#IBAction func buttonAddArtist(_ sender: UIButton) {
addArtist()
}
override func viewDidLoad() {
super.viewDidLoad()
//getting a reference to the node artists
refArtists = FIRDatabase.database().reference().child("artists")
//observing the data changes
refArtists.observe(FIRDataEventType.value, with: { (snapshot) in
//if the reference have some values
if snapshot.childrenCount > 0 {
//clearing the list
self.artistList.removeAll()
//iterating through all the values
for artists in snapshot.children.allObjects as! [FIRDataSnapshot] {
//getting values
let artistObject = artists.value as? [String: AnyObject]
let artistName = artistObject?["artistName"]
let artistId = artistObject?["id"]
let artistGenre = artistObject?["artistGenre"]
let artistImg = artistObject?["artistImg"]
//creating artist object with model and fetched values
let artist = ArtistModel(id: artistId as! String?, name: artistName as! String?, genre: artistGenre as! String?, img: artistImg as! String?)
//appending it to list
self.artistList.append(artist)
}
//reloading the tableview
self.tableViewArtists.reloadData()
}
})
}
func addArtist(){
//generating a new key inside artists node
//and also getting the generated key
let key = refArtists.childByAutoId().key
//creating artist with the given values
let artist = ["id":key,
"artistName": textFieldName.text! as String,
"artistGenre": textFieldGenre.text! as String,
] //as [String : Any]
//adding the artist inside the generated unique key
refArtists.child(key).setValue(artist)
//displaying message
labelMessage.text = "Artist Added"
}
func updateArtist(id:String, name:String, genre:String){
//creating artist with the new given values
let artist = ["id":id,
"artistName": name,
"artistGenre": genre
]
//updating the artist using the key of the artist
refArtists.child(id).setValue(artist)
//displaying message
labelMessage.text = "Artist Updated"
}
First I'd cache the image.
let imageCache = NSCache<AnyObject, AnyObject>()
extension UIImageView {
func loadImageFromCacheWithUrlSting(urlString: String) {
self.image = nil
if let cachedImage = imageCache.object(forKey: urlString as AnyObject) as? UIImage {
self.image = cachedImage
return
}
let url = URL(string: urlString)
URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error != nil {
print(error!)
}
DispatchQueue.main.async {
if let downloadedImage = UIImage(data: data!) {
imageCache.setObject(downloadedImage, forKey: urlString as AnyObject)
self.image = downloadedImage
}
}
}.resume()
}
This will save you downloading the image every time the view loads. Then add something like this to cellForRow
if let artistImage = dictionary["img"] as? String {
cell?.artistImageView.loadImageFromCacheWithUrlSting(urlString: artistImage)
}

can you spot the nil while unwrapping?

I'm trying to find where is the nil when unwrapping. Here is the piece of code I have. The lines where the fatal errors are found are at:
1st file:
date = dateFormatter().date(from: dictionary[kDATE] as! String)!
2nd file:
self.allLists.append(ShoppingList.init(dictionary: currentList))
This is from the shoppingList.swift file and the function is called in a controller
import Foundation
import Firebase
class ShoppingList{
let name: String
var totalPrice: Float
var totalItems: Int
var id: String
var date: Date
var ownerId: String
init(_name: String, _totalPrice: Float = 0, _id: String = "") {
name = _name
totalPrice = _totalPrice
totalItems = 0
id = _id
date = Date()
ownerId = "1234"
}
//creates shopping list item from this dictionary
init(dictionary: NSDictionary) {
name = dictionary[kNAME] as! String
totalPrice = dictionary[kTOTALPRICE] as! Float
totalItems = dictionary[kTOTALITEMS] as! Int
id = dictionary[kSHOPPINGLISTID] as! String
date = dateFormatter().date(from: dictionary[kDATE] as! String)!
ownerId = dictionary[kOWNERID] as! String
}
func dictionaryFromItem(item: ShoppingList) -> NSDictionary {
return NSDictionary(objects: [item.name, item.totalPrice, item.totalItems, item.id, dateFormatter().string(from: item.date), item.ownerId], forKeys: [kNAME as NSCopying, kTOTALPRICE as NSCopying, kTOTALITEMS as NSCopying, kSHOPPINGLISTID as NSCopying, kDATE as NSCopying, kOWNERID as NSCopying])
}
Here is the controller:
import UIKit
import KRProgressHUD
class AllListsViewController: UIViewController, UITableViewDataSource,UITableViewDelegate{
#IBOutlet weak var tableView: UITableView!
var allLists:[ShoppingList] = []
var nameTextField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
loadLists()
}
//MARK: TableView DataSource
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return allLists.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
let shoppingList = allLists[indexPath.row]
cell.textLabel?.text = shoppingList.name
return cell
}
//MARK: IBActions
#IBAction func addBarButonItemPressed(_ sender: Any) {
let alertController = UIAlertController(title: "Create Shopping List", message: "Enter the shopping list name", preferredStyle: .alert)
alertController.addTextField{ (nameTextField) in
nameTextField.placeholder = "Name"
self.nameTextField = nameTextField
}
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel){ (action) in
}
let saveAction = UIAlertAction(title: "Save", style: .default){ (action) in
if self.nameTextField.text != ""{
self.createShoppingList()
}else{
KRProgressHUD.showWarning(message: "Name is empty!")
}
}
alertController.addAction(cancelAction)
alertController.addAction(saveAction)
self.present(alertController,animated: true, completion:nil)
}
//MARK: LoadList
func loadLists(){
//.values has all the info of the child
firebase.child(kSHOPPINGLIST).child("1234").observe(.value, with: {
snapshot in
self.allLists.removeAll()
//if we actually received smthing from firebase
if snapshot.exists(){
let sorted = ((snapshot.value as! NSDictionary).allValues as NSArray).sortedArray(using: [NSSortDescriptor(key: kDATE,ascending: false)])
for list in sorted {
let currentList = list as! NSDictionary
self.allLists.append(ShoppingList.init(dictionary: currentList))
}
} else {
print("no snapshot")
}
self.tableView.reloadData()
})
}
//MARK: Helper functions
func createShoppingList(){
let shoppingList = ShoppingList(_name: nameTextField.text!)
shoppingList.saveItemInBackground(shoppingList: shoppingList){ (error) in
if error != nil{
KRProgressHUD.showError(message: "Error creating shopping list")
return
}
}
}
}
Also the data formatter is a small function in another file.
import Foundation
import UIKit
private let dateFormat = "yyyyMMDDHHmmss"
func dateFormatter() -> DateFormatter {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = dateFormat
return dateFormatter
}
So you have a forced downcast and a forced optional unwrap on this line:
date = dateFormatter().date(from: dictionary[kDATE] as! String)!
Either your dictionary isn't returning a string, or the string coming out of the dictionary isn't able to be processed as a date. My guess is it's the first problem as dates are often stored as epoch.
Try this instead of the line above. Add a breakpoint at the top and step through:
print(dictionary[kDATE])
if let dictValue = dictionary[kDATE] as? String {
print(dictValue)
if let unwrappedDate = dateFormatter().date(from: dictValue) {
date = unwrappedDate
}
}
If it fails on the first if-let then the return value is not a string. If it fails on the second the problem lies with the date formatter being unable to read the format.
The first print might give you a clue as to what type to cast to, the second could help you fix the format.
Try to be careful when force unwrapping,
optionalVar!
or for downcasting.
unknownType as! Type
You should really only "use the force" when you're absolutely sure there's no way the value will be nil.

How to use a detail disclosure button in UICollectionViewCell?

I am trying to use the detail disclosure button on the view controller pictured below (PhotoSearchViewController) to show a UIAlertController that shows the photo description from the ImageUrlItem.
When I click the button, it shows that it was pressed, but the alert containing the description does not appear.
My code for the ImageUrlItem:
import Foundation
import Firebase
struct ImageUrlItem {
//Should I add my description in here?
let key: String
let imageUrl: String
let watsonCollectionImageUrl: String
let score: Double
let description: String
let ref: FIRDatabaseReference?
init(imageUrl: String, key: String = "", watsonCollectionImageUrl: String = "", score: Double = 0, description: String) {
self.key = key
self.imageUrl = imageUrl
self.watsonCollectionImageUrl = watsonCollectionImageUrl
self.ref = nil
self.score = score
self.description = description
}
init(snapshot: FIRDataSnapshot) {
key = snapshot.key
let snapshotValue = snapshot.value as! [String: AnyObject]
imageUrl = snapshotValue["ImageUrl"] as! String // must map to firebase names
watsonCollectionImageUrl = ""
score = 0
description = snapshotValue["Description"] as! String
ref = snapshot.ref
}
func toAnyObject() -> Any {
return [
"imageUrl": imageUrl,
"Description": description
]
}
}
My code for the collection view cell (PhotoCell) is as follows:
class PhotoCell: UICollectionViewCell, UIAlertViewDelegate
{
var photos: [ImageUrlItem] = []
var ref = FIRDatabase.database().reference(withPath: "Photos")
#IBOutlet weak var imgPhoto: UIImageView!
#IBOutlet weak var lblScore: UILabel!
#IBAction func btnDesc(_ sender: UIButton)
{
let alertTitle = "Description"
let alertMessage = photos.description
let alertController = UIAlertController(title: alertTitle, message: alertMessage, preferredStyle: UIAlertControllerStyle.alert)
let okAction = UIAlertAction(title:"Ok", style: UIAlertActionStyle.default)
{
(result : UIAlertAction) -> Void in
print("OK")
}
alertController.addAction(okAction)
self.parentViewController?.present(alertController, animated: true, completion: nil)
}
}
Code for the cellForRowAt in my PhotoSearchViewController:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let reuseIdentifier = "PhotoCell"
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath as IndexPath) as! PhotoCell
cell.backgroundColor = UIColor(red:0.74, green:0.76, blue:0.78, alpha: 1.0)
// Do any custom modifications you your cell, referencing the outlets you defined in the Custom cell file // if we have a label IBOutlet in PhotoCell we can customize it here
// on page load when we have no search results, show nothing
if similarImageUrls.count > 0 {
//print(indexPath.row)
//print(similarImageUrls.count)
if (indexPath.row < similarImageUrls.count){
let image = self.similarImageUrls[indexPath.row]
// get image asynchronously via URL
let url = URL(string: image.imageUrl)
DispatchQueue.global().async {
// make an asynchonorous call to load the image
DispatchQueue.main.async {
cell.imgPhoto.af_setImage(withURL: url!) // show image using alamofire
}
}
cell.lblScore.isHidden = false
cell.lblScore.text = "Score: \(NSString(format: "%.2f", (image.score * 100)) as String)%"
}
else
{
// show the placeholder image instead
cell.imgPhoto.image = UIImage(named: "person")
cell.lblScore.isHidden = true
cell.lblScore.text = "0.00%"
}
}
else
{
// show the placeholder image instead
cell.imgPhoto.image = UIImage(named: "person")
cell.lblScore.isHidden = true
cell.lblScore.text = "0.00%"
// when we get to the last image, and it is not the first time load
if (indexPath.row == 8 && !firstTimeSearch){
// show nothing found alert here
let ac = UIAlertController(title: "Photo Search Completed!", message:"No macthing photo found!", preferredStyle: .alert)
ac.addAction(UIAlertAction(title: "OK", style: .default))
self.present(ac, animated: true)
}
}
return cell
}
}
You can use custom delegate to show the alert
// here is the protocol for creating the delegation:
protocol BtnDesc : class {
func btnDecAlert(alertTitle: String, message: String)
}
class PhotoCell: UICollectionViewCell
{
// MARK:- Delegate
weak var btnDescDelegate : BtnDesc?
var photos: [ImageUrlItem] = []
var ref = FIRDatabase.database().reference(withPath: "Photos")
#IBOutlet weak var imgPhoto: UIImageView!
#IBOutlet weak var lblScore: UILabel!
#IBAction func btnDesc(_ sender: UIButton)
{
let alertTitle = "Description"
let alertMessage = photos.description
btnDescDelegate?.btnDecAlert(alertTitle: alertTitle, message: alertMessage)
}
}
Code for the cellForRowAt in PhotoSearchViewController:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let reuseIdentifier = "PhotoCell"
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath as IndexPath) as! PhotoCell
//Mark set the delegate.
cell.btnDescDelegate = self
.....
//conform delegate
extension PhotoSearchViewController : BtnDesc {
func btnDecAlert(alertTitle: String, message: String) {
let alert = UIAlertController(title: alertTitle, message: message, preferredStyle: .alert)
let okAction = UIAlertAction(title: "Ok", style: .default, handler: {(_ action: UIAlertAction) -> Void in
// here is your action
print("OK")
})
alert.addAction(okAction)
self.present(alert, animated: true, completion: nil)
}
}

Resources