I want my app to show data from mysql database in collectionview so i write down the mysql connection code in viewDidload() and make it in array format everything work fine except for showing data in collectionview Here is my code
var subjectlist = ""
var imgurllist = ""
var subjectarray = [String]()
var imgurllistarray = [String]()
override func viewDidLoad() {
super.viewDidLoad()
collectionview.dataSource = self
collectionview.delegate = self
let request = NSMutableURLRequest(url: NSURL(string: [Point to file on my server])! as URL)
let urlsession = URLSession.shared.dataTask(with: request as URLRequest, completionHandler: {data, response, error -> Void in
DispatchQueue.main.async {
do {
if let dataunwarp = data {
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary
if let parseJSON = json {
if let subjectnotfound = parseJSON["subjectnotfound"]{
let alert = UIAlertController(title: "เกิดข้อผิดพลาด", message: "ไม่พบ subject ในเซิฟเวอร์ กรุณาลองใหม่อีกครั้ง", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "ปิด", style: .default, handler: { (alert: UIAlertAction!) in
self.navigationController?.popViewController(animated: true)
}))
self.dismiss(animated: false, completion: { () in self.present(alert, animated: true, completion: nil) })
} else {
self.subjectlist = parseJSON["subjectlist"] as! String
self.imgurllist = parseJSON["imgurllist"] as! String
self.subjectarray = Array(self.subjectlist.components(separatedBy: ","))
print("subjectarray=\(self.subjectarray)")
print(self.subjectarray[0])
//self.imgurllistarray = Array(self.imgurllist.components(separatedBy: ","))
}
} else {
let alert = UIAlertController(title: "เกิดข้อผิดพลาด", message: "เกิดข้อผิดพลาดจากเซิฟเวอร์", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "ปิด", style: .default, handler: { (alert: UIAlertAction!) in
self.navigationController?.popViewController(animated: true)
}))
self.dismiss(animated: false, completion: { () in self.present(alert, animated: true, completion: nil) })
}
} else {
let alert = UIAlertController(title: "เกิดข้อผิดพลาด", message: "เกิดข้อผิดพลาดจากเซิฟเวอร์", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "ปิด", style: .default, handler: { (alert: UIAlertAction!) in
self.navigationController?.popViewController(animated: true)
}))
self.dismiss(animated: false, completion: { () in self.present(alert, animated: true, completion: nil) })
}
} catch {
let alert = UIAlertController(title: "เกิดข้อผิดพลาด", message: "ไม่สามารถติดต่อกับเซิฟเวอร์ได้ กรุณาตรวจสอบการเชื่อมต่ออินเตอร์เน็ตเเละลองใหม่อีกครั้ง", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "ปิด", style: .default, handler: { (alert: UIAlertAction!) in
self.navigationController?.popViewController(animated: true)
}))
self.dismiss(animated: false, completion: { () in self.present(alert, animated: true, completion: nil) })
}
}})
urlsession.resume()
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return subject.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! collectionviewcell
let subjectimgurl = NSMutableURLRequest(url: NSURL(string: "https://psmapps.com/psmatstamp/images/atstamp.png")! as URL)
let imgsession = URLSession.shared.dataTask(with: subjectimgurl as URLRequest, completionHandler: {data, response, error -> Void in
if let e = error {
print("Error downloading picture: \(e)")
} else {
// No errors found.
// It would be weird if we didn't have a response, so check for that too.
if let res = response as? HTTPURLResponse {
if let imageData = data {
// Finally convert that Data into an image and do what you wish with it.
let subjectimg = UIImage(data: imageData)
DispatchQueue.main.async {
cell.subjectimage.image = subjectimg
}
} else {
print("Couldn't get image: Image is nil")
}
} else {
print("Couldn't get response code for some reason")
}
}
})
imgsession.resume()
cell.subjectlabel.text = subjectarray[indexPath.item]
print(self.subjectarray[indexPath.item])
cell.layer.borderColor = UIColor.lightGray.cgColor
cell.layer.borderWidth = 0.5
return cell
}
An error occurred in this line before this view presented
cell.subjectlabel.text = subjectarray[indexPath.item]
with error message: "Index out of range" but when i uncomment the line that error occurred and print out the data from subjectarray, i can see my data with array type
How can i fix this problem?
Inside numberOfItemsInSection
return subject.count
Then inside cellForItemAt you use
subjectarray[indexPath.item]
Is there a guarantee that both will be same size at a time , also use SDWebImage to load images
Also after you get the data reload
self.subjectarray = Array(self.subjectlist.components(separatedBy: ","))
self.collectionView.reloadData()
plus spreading UIAlertController everywhere isn't a good way for compact code , consider making an extension / function and call it with the appropriate message
Related
I have an app that takes user inputs in an alertController, and then this values will be stored in CoreData which is then displayed in a tableview. I concatenated all the strings to gather with a comma as separator to make it easier for me to export a csv. However, when I print out the CoreData entity, I get an array that is quite complicated. The array looks like this:
[ (entity: AlarmItems; id: 0xc2bccd37cb753acb ; data: {
alarmAttributes = "Example Name, 24/11/2019, 1500, True, NIL";
}), (entity: AlarmItems; id: 0xc2bccd37cb653acb ; data: {
alarmAttributes = "Example , 12/12/2019, 24/11/2019, True, NIL";
})]
I would like to pull out just that parts after alarmAttributes to be exported into a CSV for further use.
I looked at NSEntityMapping but that did not help me. I'm quite stuck right now. I do not know how to approach the problem. Is my approach even correct in the first place? Is it even possible to export a csv using a an array that I create? The idea is to have the csv be stored in the iOS Device which can then be emailed elsewhere.
My ViewController:
class ViewController: UITableViewController {
var alarmItems: [NSManagedObject] = []
let cellId = "cellId"
override func viewDidLoad() {
super.viewDidLoad()
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
let managedContext = appDelegate.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "AlarmItems")
do {
alarmItems = try managedContext.fetch(fetchRequest)
} catch let err as NSError {
print("Failed to fetch items", err)
}
}
#objc func addAlarmItem(_ sender: AnyObject) {
print("this works")
let alertController = UIAlertController(title: "Add New Item", message: "Please fill in the blanks", preferredStyle: .alert)
let saveAction = UIAlertAction(title: "Save", style: .default) { [unowned self] action in
//combined string of attributes
let myStrings: [String] = alertController.textFields!.compactMap { $0.text }
let myText = myStrings.joined(separator: ", ")
self.save(myText)
self.tableView.reloadData()
}
let cancelAction = UIAlertAction(title: "Cancel", style: .destructive, handler: nil)
alertController.addTextField { (textField) in
textField.placeholder = "Enter Name of Engineer"
}
alertController.addTextField { (textField) in
textField.placeholder = "Enter Date of Alarm in DD/MM/YYYY"
}
alertController.addTextField { (textField) in
textField.placeholder = "Enter Time of Alarm in 24h (eg: 2300)"
}
alertController.addTextField { (textField) in
textField.placeholder = "Please indicate True/False (type True or False)"
}
alertController.addTextField { (textField) in
textField.placeholder = "Insert comments (if any), or NIL"
}
alertController.addAction(saveAction)
alertController.addAction(cancelAction)
present(alertController, animated: true, completion: nil)
}
func save(_ itemName: String) {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
let managedContext = appDelegate.persistentContainer.viewContext
let entity = NSEntityDescription.entity(forEntityName: "AlarmItems", in: managedContext)!
let item = NSManagedObject(entity: entity, insertInto: managedContext)
item.setValue(itemName, forKey: "alarmAttributes")
do {
try managedContext.save()
alarmItems.append(item)
} catch let err as NSError {
print("Failed to save an item", err)
}
}
#objc func exportCSV(_ sender: AnyObject) {
//will work on exporting csv in the future
return
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
return alarmItems.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath)
let alarmItem = alarmItems[indexPath.row]
cell.textLabel?.text = alarmItem.value(forKeyPath: "alarmAttributes") as? String
return cell
}
}
I have a chat feature within my app. The page loads the existing data as an array called hhmessages. The last message in the conversation was 'Welcome', when I enter new text 'Thank You' and hit enter the table automatically displays 'Welcome' again instead of 'Thank You.' If it exit out of the page and come back it now shows 'Thank You' as the last message. It's working on the backend just the instant update isn't showing the value in the UITableView when entered.
This is for an iPhone app.
UPDATED TO SHOW COMPLETE CODE - Now that reverse has been removed the new entry shows as blank.
import UIKit
import Foundation
extension String {
// Calculeta the hight string Function
func calculateTextFrameRect(
objectsInPlaceHeight: CGFloat,
objectsInPlaceWidth: CGFloat,
fontSize: CGFloat,
fontWeight: CGFloat) -> CGSize
{
let bounding = CGSize(width: UIScreen.main.bounds.width - objectsInPlaceWidth, height: .infinity)
let rect = NSString(string: self).boundingRect(
with: bounding,
options: NSStringDrawingOptions.usesFontLeading.union(NSStringDrawingOptions.usesLineFragmentOrigin),
attributes: [NSAttributedStringKey.font: UIFont.systemFont(ofSize: fontSize, weight: UIFont.Weight(rawValue: fontWeight))],
context: nil)
return CGSize(width: UIScreen.main.bounds.width, height: rect.height + objectsInPlaceHeight )
}
}
// Messages for test
let frame = CGRect(origin: .zero, size: CGSize.init(width: 375, height: 559))
class Message {
let message: String
var incoming: [Int]
let image: UIImage
var avas = UIImage()
init(message: String, image: UIImage, incoming: Int, avas: UIImage) {
self.message = message
self.image = image
self.incoming = [incoming]
self.avas = avas
}
}
class ConversationViewController: UIViewController, UINavigationControllerDelegate, UIImagePickerControllerDelegate, UITableViewDelegate, UITableViewDataSource, UITextViewDelegate {
//var user = NSDictionary()
var messages = NSDictionary()
//var guest = NSDictionary()
#IBOutlet var senderLbl: UILabel!
#IBOutlet var senderageLbl: UILabel!
#IBOutlet var senderraceLbl: UILabel!
#IBOutlet var sendergenderLbl: UILabel!
#IBOutlet var sendercityLbl: UILabel!
#IBOutlet var senderstateLbl: UILabel!
// #IBOutlet var tableView: UITableView!
var hhmessages = [AnyObject]()
//var messages: [Message] = []
var pictures = [UIImage]()
var avas = [UIImage]()
var avaURL = [String]()
var isLoading = false
var skip = 0
var limit = 50
var images = [UIImage]()
var incoming: [Int] = []
var comments = [String]()
var ids = [String]()
//var isSent: String = ""
var isPictureSelected = false
//var currentUser_ava = [Any]()
#IBOutlet var pictureImg: UIImageView!
#IBOutlet var avaImg: UIImageView!
#IBOutlet var viewprofile_btn: UIButton!
#IBOutlet var imgprofile_btn: UIButton!
#IBOutlet var replyTxt: UITextView!
//var replyTxt:UITextView!
#IBOutlet var replyTxt_height: NSLayoutConstraint!
#IBOutlet var replyTxt_bottom: NSLayoutConstraint!
#IBOutlet var picSelect: UIButton!
#IBOutlet var replyBtn: UIButton!
var puuid = String()
var imageSelected = false
var coolIndicator: UIActivityIndicatorView!
var commentsTextView_bottom_identity = CGFloat()
#IBOutlet var tableView: UITableView!
// Table View here + basic configuration
override func viewDidLoad() {
//self.tabBarController?.tabBar.isHidden = true
super.viewDidLoad()
tableView.transform = CGAffineTransform(rotationAngle: -(CGFloat)(Double.pi));
// dynamic cell height
tableView.dataSource = self
tableView.delegate = self
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 200
loadPosts()
replyTxt.layer.cornerRadius = replyTxt.bounds.width / 50
replyTxt.backgroundColor = UIColor.clear
replyTxt.layer.borderColor = UIColor.gray.cgColor
replyTxt.layer.borderWidth = 1.0
let username = messages["sender"] as? String
self.navigationItem.title = username
}
// pre last func
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// remove observers of notification when the viewController is left
NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIKeyboardWillHide, object: nil)
}
// exec-d once notification is caught -> KeyboardWillShow
#objc func keyboardWillShow(_ notification: Notification) {
// getting the size of the keyboard
if let keyboard_size = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
// increasing the bottom constraint by the keyboard's height
replyTxt_bottom.constant += keyboard_size.height
}
// updating the layout with animation
UIView.animate(withDuration: 0.3) {
self.view.layoutIfNeeded()
}
}
// exec-d once notification is caught -> KeyboardWillHide
#objc func keyboardWillHide() {
// updating the layout with animation
UIView.animate(withDuration: 0.3) {
self.view.layoutIfNeeded()
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
//replyTxt().resignFirstResponder()
self.view.endEditing(false)
}
// TABLEVIEW
// Number os cells
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return hhmessages.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let colorSmoothGray = UIColor(red: 229/255, green: 229/255, blue: 234/255, alpha: 1)
let colorBrandBlue = UIColor(red: 148 / 255, green: 33 / 255, blue: 147 / 255, alpha: 1)
let pictureURL = hhmessages[indexPath.row]["uploadpath"] as? String
print("test 1", pictureURL)
// no picture in the post
if pictureURL == nil || pictureURL == "" {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! ConversationCell
cell.transform = CGAffineTransform(rotationAngle: CGFloat(Double.pi))
// shortcuts
let hhpost = hhmessages[indexPath.row]
let text = hhpost["messagetext"] as? String
cell.messageLbl.text = text
cell.messageLbl.textAlignment = .right
cell.messageLbl.backgroundColor = colorSmoothGray
cell.messageLbl.textColor = .black
cell.messageLbl.font = UIFont.preferredFont(forTextStyle: UIFontTextStyle.body)
cell.messageLbl.font?.withSize(25)
cell.messageLbl.clipsToBounds = true
cell.messageLbl.sizeToFit()
pictures.append(UIImage())
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "PicCell", for: indexPath) as! PicConversationCell
cell.transform = CGAffineTransform(rotationAngle: CGFloat(Double.pi))
let smimages = hhmessages[indexPath.row]["path"] as? UIImage
cell.smavaImg.image = smimages
for i in 0 ..< self.incoming.count {
// Confiture the constraints for cell
if self.incoming[indexPath.row] == 1 {
// Text
cell.messageLbl.textAlignment = .left
cell.messageLbl.backgroundColor = colorBrandBlue
cell.messageLbl.textColor = .white
cell.messageLbl.font = UIFont.preferredFont(forTextStyle: UIFontTextStyle.body)
cell.messageLbl.font?.withSize(25)
cell.messageLbl.clipsToBounds = true
// Constraints
cell.lefBubbleConstraint.isActive = true
cell.rightBubbleConstraint.isActive = false
if cell.postpictureImg.image == nil {
cell.postpictureImg.backgroundColor = .black
cell.postpictureImg.clipsToBounds = true
}
else {
cell.postpictureImg.backgroundColor = .black
cell.postpictureImg.clipsToBounds = true
}
}
else if self.incoming[indexPath.row] == 0 {
// Text
cell.messageLbl.textAlignment = .right
cell.messageLbl.backgroundColor = colorSmoothGray
cell.messageLbl.textColor = .black
cell.messageLbl.font = UIFont.preferredFont(forTextStyle: UIFontTextStyle.body)
cell.messageLbl.font?.withSize(25)
cell.messageLbl.clipsToBounds = true
// Constraints
cell.lefBubbleConstraint.isActive = false
cell.rightBubbleConstraint.isActive = true
if cell.postpictureImg.image == nil {
cell.postpictureImg.backgroundColor = .black
cell.postpictureImg.clipsToBounds = true
}
else {
cell.postpictureImg.backgroundColor = .black
cell.postpictureImg.clipsToBounds = true
}
}
if cell.lefBubbleConstraint.isActive == true {
cell.lefImageConstraint.isActive = true
cell.rightImageConstraint.isActive = false
} else {
cell.lefImageConstraint.isActive = false
cell.rightImageConstraint.isActive = true
}
let pictureString = hhmessages[indexPath.row]["uploadpath"] as! String
let pictureURL = URL(string: pictureString)!
// if there are still pictures to be loaded
if hhmessages.count != pictures.count {
URLSession(configuration: .default).dataTask(with: pictureURL) { (data, response, error) in
// downloaded
if let image = UIImage(data: data!) {
self.pictures.append(image)
DispatchQueue.main.async {
cell.postpictureImg.image = image
}
}
}.resume()
// cached picture
} else {
DispatchQueue.main.async {
cell.postpictureImg.image = self.pictures[indexPath.row]
}
}
}
return cell
}
}
// pre load func
override func viewDidAppear(_ animated: Bool) {
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
// func of loading posts from server
#objc func loadPosts() {
//isLoading = true
let me = user!["username"] as! String
let meid = user!["id"] as! String
print(meid)
print(me)
//print(username)
let uuid = messages["uuid"] as! String
print(uuid)
// accessing php file via url path
let url = URL(string: "http://localhost/message.php")!
// pass information to php file
let body = "username=\(me)&uuid=\(uuid)&recipient_id=\(meid)"
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = body.data(using: String.Encoding.utf8)
tableView.transform = CGAffineTransform(rotationAngle: -(CGFloat)(Double.pi));
// launch session
URLSession.shared.dataTask(with: request) { (data, response, error) in
DispatchQueue.main.async {
// no error of accessing php file
// error occured
if error != nil {
Helper().showAlert(title: "Server Error", message: error!.localizedDescription, in: self)
//self.isLoading = false
return
}
do {
// access data - safe mode
guard let data = data else {
Helper().showAlert(title: "Data Error", message: error!.localizedDescription, in: self)
//self.isLoading = false
return
}
// getting content of $returnArray variable of php file
let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? NSDictionary
// accessing json data - safe mode
guard let posts = json?["messages"] as? [NSDictionary] else {
//self.isLoading = false
return
}
// assigning all successfully loaded posts to our Class Var - posts (after it got loaded successfully)
self.hhmessages = posts
self.tableView.reloadData()
// scroll to the latest index (latest cell -> bottom)
let indexPath = IndexPath(row: self.hhmessages.count - 1, section: 0)
self.tableView.scrollToRow(at: indexPath, at: .bottom, animated: true)
// self.isLoading = false
} catch {
Helper().showAlert(title: "JSON Error", message: error.localizedDescription, in: self)
//self.isLoading = false
return
}
}
}.resume()
}
#IBAction func viewprofile_clicked(_ sender: Any) {
// performSegue(withIdentifier: "guest2", sender: self.guest)
}
#IBAction func imgprofile_clicked(_ sender: Any) {
// performSegue(withIdentifier: "guest2", sender: self.guest)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "guest" {
if let destination = segue.destination as? GuestViewController {
destination.guest = messages
}
}
}
// function sending requset to PHP to uplaod a file
func uploadPost() {
// validating vars before sending to the server
guard let user_id = user?["id"] as? String, let username = user?["username"] as? String, let avaPath = user?["ava"] else {
// converting url string to the valid URL
if let url = URL(string: user?["ava"] as! String) {
// downloading all data from the URL
guard let data = try? Data(contentsOf: url) else {
return
}
// converting donwloaded data to the image
guard let image = UIImage(data: data) else {
return
}
// assigning image to the global var
let currentUser_ava = image
}
return
}
let user_id_int = Int(user_id)!
let messagetext = replyTxt.text.trimmingCharacters(in: .whitespacesAndNewlines)
hhmessages.insert(messagetext as AnyObject, at: hhmessages.endIndex)
let indexPath = IndexPath(row: hhmessages.count - 1, section: 0)
tableView.beginUpdates()
tableView.insertRows(at: [indexPath], with: .automatic)
tableView.endUpdates()
tableView.transform = CGAffineTransform(rotationAngle: -(CGFloat)(Double.pi));
tableView.scrollToRow(at: indexPath, at: .bottom, animated: true)
replyTxt.text = ""
textViewDidChange(replyTxt)
let recipient = messages["username"] as! String
let rid = String(describing: messages["recipient_id"]!)
let uuid = messages["uuid"] as! String
puuid = UUID().uuidString
// prepare request
let url = URL(string: "http://localhost/messagepost.php")!
let body = "sender_id=\(user_id)&sender=\(username)&text=\(messagetext)&recipient_id=\(rid)&recipient=\(recipient)&uuid=\(uuid)&puuid=\(puuid)"
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = body.data(using: .utf8)
// send request
URLSession.shared.dataTask(with: request) { (data, response, error) in
DispatchQueue.main.async {
// error happened
if error != nil {
Helper().showAlert(title: "Server Error", message: error!.localizedDescription, in: self)
return
}
do {
// converting received data from the server into json format
let json = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? NSDictionary
// safe mode of casting json
guard let parsedJSON = json else {
return
}
// if the status of JSON is 200 - success
if parsedJSON["status"] as! String == "200" {
} else {
Helper().showAlert(title: "400", message: parsedJSON["status"] as! String, in: self)
return
}
// json error
} catch {
Helper().showAlert(title: "JSON Error", message: error.localizedDescription, in: self)
return
}
}
}.resume()
}
// exec-d whenever delegated textView has been changed by chars
func textViewDidChange(_ textView: UITextView) {
// declaring new size of the textView. we increase the height
let new_size = textView.sizeThatFits(CGSize.init(width: textView.frame.width, height: CGFloat(MAXFLOAT)))
// assign new size to the textView
textView.frame.size = CGSize.init(width: CGFloat(fmaxf(Float(new_size.width), Float(textView.frame.width))), height: new_size.height)
//UIView.animate(withDuration: 0.2) {
self.view.layoutIfNeeded()
//}
}
#IBAction func picSelect_clicked(_ sender: AnyObject) {
// calling picker for selecting iamge
showActionSheet()
}
// this function launches Action Sheet for the photos
func showActionSheet() {
// declaring action sheet
let sheet = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
// declaring camera button
let camera = UIAlertAction(title: "Camera", style: .default) { (action) in
// if camera available on device, than show
if UIImagePickerController.isSourceTypeAvailable(.camera) {
self.showPicker(with: .camera)
}
}
// declaring library button
let library = UIAlertAction(title: "Photo Library", style: .default) { (action) in
// checking availability of photo library
if UIImagePickerController.isSourceTypeAvailable(.photoLibrary) {
self.showPicker(with: .photoLibrary)
}
}
// declaring cancel button
let cancel = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
// adding buttons to the sheet
sheet.addAction(camera)
sheet.addAction(library)
sheet.addAction(cancel)
// present action sheet to the user finally
self.present(sheet, animated: true, completion: nil)
}
// takes us to the PickerController (Controller that allows us to select picture)
func showPicker(with source: UIImagePickerControllerSourceType) {
let picker = UIImagePickerController()
picker.delegate = self
picker.allowsEditing = true
picker.sourceType = source
present(picker, animated: true, completion: nil)
}
// executed whenever the image has been picked via pickerController
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
// accessing selected image
let image = info[UIImagePickerControllerEditedImage] as? UIImage
// assigning selected image to pictureImageView
pictureImg.image = image
// cast boolean as TRUE -> Picture Is Selected
isPictureSelected = true
// remove pickerController
pictureImg.image = info[UIImagePickerControllerEditedImage] as? UIImage
self.dismiss(animated: true, completion: nil)
// cast as a true to save image file in server
if pictureImg.image == info[UIImagePickerControllerEditedImage] as? UIImage {
imageSelected = true
}
dismiss(animated: true, completion: nil)
}
// exec when pictureImageView has been tapped
#IBAction func pictureImageView_tapped(_ sender: Any) {
// declaring action sheet
let sheet = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
// declaring delete button
let delete = UIAlertAction(title: "Delete", style: .destructive) { (action) in
self.pictureImg.image = UIImage()
}
// declaring cancel button
let cancel = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
// adding buttons to the sheet
sheet.addAction(delete)
sheet.addAction(cancel)
// present action sheet to the user finally
self.present(sheet, animated: true, completion: nil)
}
// custom body of HTTP request to upload image file
func createBodyWithParams(_ parameters: [String: String]?, filePathKey: String?, imageDataKey: Data, boundary: String) -> Data {
let body = NSMutableData();
if parameters != nil {
for (key, value) in parameters! {
body.appendString("--\(boundary)\r\n")
body.appendString("Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n")
body.appendString("\(value)\r\n")
}
}
// if file is not selected, it will not upload a file to server, because we did not declare a name file
var filename = ""
if imageSelected == true {
filename = "notes-\(puuid).jpg"
}
let mimetype = "image/jpg"
body.appendString("--\(boundary)\r\n")
body.appendString("Content-Disposition: form-data; name=\"\(filePathKey!)\"; filename=\"\(filename)\"\r\n")
body.appendString("Content-Type: \(mimetype)\r\n\r\n")
body.append(imageDataKey)
body.appendString("\r\n")
body.appendString("--\(boundary)--\r\n")
return body as Data
}
#IBAction func replyBtn_clicked(_ sender: Any) {
if replyTxt.text.isEmpty == false && replyTxt.text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == false {
uploadPost()
//tableView.reloadData()
}
}
#objc func keyboardWillChange(notification: NSNotification) {
let duration = notification.userInfo![UIKeyboardAnimationDurationUserInfoKey] as! Double
let curve = notification.userInfo![UIKeyboardAnimationCurveUserInfoKey] as! UInt
let curFrame = (notification.userInfo![UIKeyboardFrameBeginUserInfoKey] as! NSValue).cgRectValue
let targetFrame = (notification.userInfo![UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
let deltaY = targetFrame.origin.y - curFrame.origin.y
//print("deltaY",deltaY)
UIView.animateKeyframes(withDuration: duration, delay: 0.0, options: UIViewKeyframeAnimationOptions(rawValue: curve), animations: {
self.replyTxt.frame.origin.y+=deltaY // Here You Can Change UIView To UITextField
self.tableView.frame.origin.y+=deltaY // Here You Can Change UIView To UITextField
self.replyBtn.frame.origin.y+=deltaY // Here You Can Change UIView To UITextField
self.picSelect.frame.origin.y+=deltaY // Here You Can Change UIView To UITextField
},completion: nil)
}
func textFieldShouldReturn(_ replyTxt: UITextField) -> Bool {
replyTxt.resignFirstResponder()
return true
}
}
the problem is you are inserting the new text at
let indexPath = IndexPath(row: hhmessages.count - 1, section: 0)
which is the last field of the array.
but in your cellForRow: you have
let text = hhmessages.reversed()[indexPath.row]["messagetext"] as! String
which shows the first field of your array in the last cell of tableView.
I think thats your problem.
Like others have told, you are messing up your order in hhmessages.
Imagine you have messages Welcome, Hello, and you are trying to add Thank Your, your code with more comments:
self.hhmessages.insert(messagetext as AnyObject, at: hhmessages.endIndex) // Thank You is inserted to the end of the array at 3rd position (2)
let indexPath = IndexPath(row: hhmessages.count - 1, section: 0) // ok, let's get index of 3rd row (2)
tableView.beginUpdates()
tableView.insertRows(at: [indexPath], with: .automatic) // add new row to the end
tableView.endUpdates()
Then later UIKit asks you to load this new 3rd row at position 2, and you call:
let text = hhmessages.reversed()[indexPath.row]["messagetext"] as! String
The problem is hhmessages.reversed() which reverses your array, and makes Thank you in fact first at position 0, and Welcome the last one at position 2, which will get the text Welcome.
The second problem I see is this line:
let pictureURL = hhmessages[indexPath.row]["uploadpath"] as? String
Here you are getting unreveresed picture url, which at least is very strange.
I have this program which takes values from firestore and puts them in an array. I have confirmed that these arrays have data inside of them by using test print functions. When I declare 2 global arrays ( gloabalGPA and globalSAT) and pass the value of the gpaColleges and the satColleges into them through a function everything works. I placed test print functions inside of the functions (the swithcSATArray and the switchGPAArray). However when I attempt to print these global variables again in a different function the print function prints out an empty array. like so: ( [] )
import UIKit
import FirebaseAuth
import FirebaseDatabase
import Firebase
import FirebaseFirestore
class ScoresViewController: UIViewController {
var docRef: DocumentReference!
let defaultStore = Firestore.firestore()
var globalGPA = [String]()
var globalSAT = [String]()
override func viewDidLoad() {
super.viewDidLoad()
let userID: String = (Auth.auth().currentUser?.uid)!
docRef = Firestore.firestore().document("Users/\(userID)")
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBOutlet weak var GpaScore: UITextField!
#IBOutlet weak var SATscore: UITextField!
#IBOutlet weak var ACT_Score: UITextField!
#IBAction func SubmitTapped(_ sender: Any) {
print("Submit Tapped")
let Sattext = SATscore.text
let Acttext = ACT_Score.text
let Gpatext = GpaScore.text
let gpaScore = Gpatext
let SatScore2 = Sattext
let Acttext2 = Acttext
let CombinedScores = Sattext! + Acttext!
if GpaScore.text == "" {
self.createAlert(titleText: "Error", messageText: "No Weighted GPA Entered")
}
else if CombinedScores == "" {
self.createAlert(titleText: "Error", messageText: "No SAT nor ACT Score Entered")
}
else{
let dataToSave: [String: Any] = ["GPA": gpaScore!, "SAT Score": SatScore2!, "ACT Score": Acttext2!]
docRef.setData(dataToSave) { (error) in
if let error = error {
print("error in sending data to fireStore: \(error.localizedDescription)")
}else {
print("Data was succesfully saved to FireStore")
}
}
self.presentLoggedInScreen()
sendToFireStore(gpa: gpaScore!, sat: SatScore2!)
self.addArrays()
}
}
func createAlert (titleText : String , messageText: String) {
let alert = UIAlertController (title: titleText, message: messageText, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Dissmis", style: .default, handler: { (action) in alert.dismiss(animated: true, completion: nil)
}))
self.present(alert, animated: true, completion: nil)
}
func presentLoggedInScreen() {
let storyboard:UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let HomeVC:HomeVC = storyboard.instantiateViewController(withIdentifier: "HomeVC") as! HomeVC
self.present(HomeVC, animated: true, completion: nil)
}
func sendToFireStore(gpa: String, sat: String) {
let db = Firestore.firestore()
var gpaColleges = [String]()
let gpaRef = db.collection("Colleges")
let query1 = gpaRef
.whereField("Average GPA", isLessThanOrEqualTo: gpa)
.getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
gpaColleges.append(document.documentID)
}
self.switchGPAArray(gpa: gpaColleges)
}
}
var satColleges = [String]()
let satRef = db.collection("Colleges")
let query2 = satRef
.whereField("Average SAT Score", isLessThanOrEqualTo: sat)
.getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
satColleges.append(document.documentID)
}
self.swithcSATArray(sat: satColleges)
}
}
}
func swithcSATArray(sat: Array<Any>) {
self.globalSAT = sat as! [String]
print("Printing inside of SAT function \(self.globalSAT)")
}
func switchGPAArray(gpa: Array<Any>) {
self.globalGPA = gpa as! [String]
print("Printing inside of GPA funtion \(self.globalGPA)")
}
func addArrays() {
print("INSIDE OF ADD ARRAYS SAT \(self.globalSAT)")
print("INSIDE OF ADD ARRAYS GPA \(self.globalSAT)")
}
}
Any help would be greatly appreciated.
First of all there is lot of code but little information about your problem. We don't even know which function you call first and what is the second one that prints empty?
I can edit this answer after you provide more details. But as far as I see, you have closures and the compiler won't wait closure to complete, to run the next line. If this is the problem you can use threads and run your code in main thread so you can be sure it is completed. Or you can use delegate pattern to be notified when it's complete.
Or just do this:
let dataToSave: [String: Any] = ["GPA": gpaScore!, "SAT Score": SatScore2!, "ACT Score": Acttext2!]
docRef.setData(dataToSave) { (error) in
if let error = error {
print("error in sending data to fireStore: \(error.localizedDescription)")
}else {
self.presentLoggedInScreen()
sendToFireStore(gpa: gpaScore!, sat: SatScore2!)
self.addArrays()
print("Data was succesfully saved to FireStore")
}
}
If no one above works, provide more details about your issue! :)
I am creating individual members in my app, and everything works well. I validate user input, store it in a local array, and then pass the data on to Firebase when I tap 'next'. The app takes me from Step 2 to Step 3. So far so good.
This is what Step 2 looks like if I load the app from scratch: Step 2 Existing Users. The users are pulled from Firebase, and everything is correct.
If I add a new user, everything updates fine. I tap the 'next' button, and Firebase updates with 3 users, all with correct data, and I'm taken to Step 3. All is good still.
But if I tap the 'back' button in the upper left corner of Step 3 (provided by my navigation controller), the app takes me back to Step 2 and now a duplicate entry of the new user shows up in my tableview. Somehow a duplicate user is appended to my array. This only shows up AFTER I click the 'back' button. It doesn't show up in Firebase, but I don't know where it's coming from. I've spent all day trying to figure this out. I'm sure it's a simple problem that I'm overlooking, but I could really use the community's help on this one.
I've looked at this but it didn't help me. I did some tests where I printed out users.count and found that on ViewDidLoad, the count was 0, but on the unwind segue, the count updated to 3 when I added a new user (this was correct, because there were 3 users). But then when I went to Step 3 and tapped the 'back' button to get back to Step 2, Step 2's ViewDidLoad showed a users.count of 4. I have no idea why. I'm completely stumped.
Here is my Step 2 View Controller code in its entirety:
class Step2VC: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var usersTableView: UITableView!
var users = [User]() // create variable called 'users' which is an array of type User (which is a class we created)
var firebaseUser: FIRUser!
var firebaseStorage: FIRStorage!
var ref: FIRDatabaseReference!
var cellStyleForEditing: UITableViewCellEditingStyle = .none
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "users"
usersTableView.dataSource = self
usersTableView.delegate = self
usersTableView.tableFooterView = UIView()
// --------
// Firebase
// --------
firebaseUser = FIRAuth.auth()?.currentUser
firebaseStorage = FIRStorage.storage()
ref = FIRDatabase.database().reference().child("users").child(firebaseUser.uid)
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "edit", style: .plain, target: self, action: #selector(editButtonTapped))
loadExistingUsers() // check to see if there are existing users, and if so, load them into tableview
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
usersTableView.reloadData()
}
// ----------
// Table View
// ----------
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return users.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as! Step2Cell
cell.myLabel.text = users[indexPath.row].firstName
cell.userImage.image = users[indexPath.row].photo
return cell
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
deleteUserConfirmationAlert(tableViewIndexPath: indexPath)
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "EditUser", sender: users[indexPath.row])
}
// ----------
// Navigation
// ----------
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "EditUser" {
let nextContoller = segue.destination as! Step2UsersVC
// 'sender' is retrieved from 'didSelectRow' function above
nextContoller.user = sender as? User
nextContoller.navBarTitle = "edit user"
} else if segue.identifier == "AddUser" {
let nextController = segue.destination as! Step2UsersVC
nextController.navBarTitle = "add user"
} else {
print("Segue Initiated:",segue.identifier!)
}
}
#IBAction func unwindToStep2VC(sender: UIStoryboardSegue) {
let sourceVC = sender.source as! Step2UsersVC
let updatedUser = sourceVC.user
if let selectedIndexPath = usersTableView.indexPathForSelectedRow {
// Update an existing user
users[selectedIndexPath.row] = updatedUser!
usersTableView.reloadData()
} else {
// Add a new user
let newIndexPath = IndexPath(row: users.count, section: 0)
users.append(updatedUser!)
usersTableView.insertRows(at: [newIndexPath], with: .automatic)
users.sort(by: {$0.birthday < $1.birthday})
usersTableView.reloadData()
}
}
#IBAction func nextButtonTapped(_ sender: UIButton) {
// check for at least two users
if users.count < 2 {
createAlert(alertTitle: "Users", alertMessage: "You have not created enough users. Please enter in at least two users.")
} else {
// check for at least one parent
if numberOfParents() < 1 {
createAlert(alertTitle: "Users", alertMessage: "You must have at least one parent. Please enter in a parent.")
} else {
confirmationAlert()
}
}
}
// ---------
// Functions
// ---------
// if users exist on Firebase, load them
func loadExistingUsers() {
ref.child("members").observe(.childAdded) { (snapshot: FIRDataSnapshot) in
if let dict = snapshot.value as? [String : Any] {
let userPhotoUrl = dict["profileImageUrl"] as! String
let userFirstName = dict["firstName"] as! String
let userBirthday = dict["birthday"] as! Int
let userPasscode = dict["passcode"] as! Int
let userGender = dict["gender"] as! String
let isUserChildOrParent = dict["childParent"] as! String
let storageRef = FIRStorage.storage().reference(forURL: userPhotoUrl)
storageRef.data(withMaxSize: 1 * 1024 * 1024, completion: { (data, error) in
let pic = UIImage(data: data!)
let user = User(profilePhoto: pic!,
userFirstName: userFirstName,
userBirthday: userBirthday,
userPasscode: userPasscode,
userGender: userGender,
isUserChildOrParent: isUserChildOrParent)
self.users.append(user)
self.users.sort(by: {$0.birthday < $1.birthday})
self.usersTableView.reloadData()
})
}
}
}
func saveUsersToFirebase() {
for user in users {
let storageRef = FIRStorage.storage().reference().child("users").child(firebaseUser.uid).child("members").child(user.firstName)
let profileImg = user.photo
let imageData = UIImageJPEGRepresentation(profileImg, 0.1) // compress photos
storageRef.put(imageData!, metadata: nil, completion: { (metadata, error) in
if error != nil {
return
}
// get Firebase image location and return the URL as a string
let profileImageUrl = (metadata?.downloadURL()?.absoluteString)!
// save user data to Firebase
self.ref?.child("members").child(user.firstName).setValue(["profileImageUrl" : profileImageUrl,
"firstName" : user.firstName,
"birthday" : user.birthday,
"passcode" : user.passcode,
"gender" : user.gender,
"childParent" : user.childParent])
})
}
}
func editButtonTapped() {
if cellStyleForEditing == .none {
cellStyleForEditing = .delete
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "done", style: .done, target: self, action: #selector(editButtonTapped))
} else {
cellStyleForEditing = .none
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "edit", style: .plain, target: self, action: #selector(editButtonTapped))
}
usersTableView.setEditing(cellStyleForEditing != .none, animated: true)
}
func deleteUserConfirmationAlert(tableViewIndexPath: IndexPath) {
// create alert for user to confirm user deletion
let alert = UIAlertController(title: "Delete User", message: "Are you sure you want to delete \(users[tableViewIndexPath.row].firstName)? This cannot be undone.", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "okay", style: .default, handler: { (action) in
alert.dismiss(animated: true, completion: nil)
// remove user from Firebase
self.ref.child("members").child(self.users[tableViewIndexPath.row].firstName).removeValue()
self.users.remove(at: tableViewIndexPath.row)
self.usersTableView.deleteRows(at: [tableViewIndexPath], with: .fade)
}))
alert.addAction(UIAlertAction(title: "cancel", style: .cancel, handler: { (action) in
alert.dismiss(animated: true, completion: nil)
self.usersTableView.reloadData()
}))
present(alert, animated: true, completion: nil)
}
func numberOfParents() -> Int {
var parentCount = 0
for user in users {
if user.childParent == "parent" {
parentCount += 1
}
}
return parentCount
}
func createAlert(alertTitle: String, alertMessage: String) {
let alert = UIAlertController(title: alertTitle, message: alertMessage, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "okay", style: .cancel, handler: { (action) in
alert.dismiss(animated: true, completion: nil)
}))
present(alert, animated: true, completion: nil)
}
func confirmationAlert() {
let alert = UIAlertController(title: "Users", message: "You have entered in \(users.count) users. Are you finished adding family members?", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "continue", style: .default, handler: { (action) in
self.saveUsersToFirebase()
self.performSegue(withIdentifier: "GoToStep3", sender: self)
alert.dismiss(animated: true, completion: nil)
}))
alert.addAction(UIAlertAction(title: "cancel", style: .cancel, handler: { (action) in
alert.dismiss(animated: true, completion: nil)
}))
present(alert, animated: true, completion: nil)
}
}
I have an Array of strings which is populating a collection view and that works well. The issue is I want to append that array with Strings that is saved in user defaults from user input textfield. I am getting the UserDefault data, The issue is it is not showing up in seperate collection view cell. it is getting attached at the end of each string in the current cells. Thanks in advance, any help would be appreciated.
This is what I tried so far:
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CollectionViewCell
let defaults = UserDefaults.standard
let value = defaults.string(forKey: "Gratitude")
print(value!)
//Array that I am trying to append with userdefault data
gratitudeArray.append(value!)
// Configure the cell
cell.cellLabel.text = gratitudeArray[indexPath.row]
return cell
}
// I am geeting user input from alert and saving in userdefaults like this:
func presentAlert() {
let alertController = UIAlertController(title: "", message: "Create your own Gratitude:", preferredStyle: .alert)
let confirmAction = UIAlertAction(title: "Save", style: .default) { (_) in
if let field = alertController.textFields?[0] {
// store data
UserDefaults.standard.set(field.text, forKey: "Gratitude")
UserDefaults.standard.synchronize()
} else {
print()
}
}
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) { (_) in }
alertController.addTextField { (textField) in
//print(textField.text!)
//textField.placeholder = ""
}
alertController.addAction(confirmAction)
alertController.addAction(cancelAction)
self.present(alertController, animated: true, completion: nil)
}
Store String in ViewDidLoad() like below:
var strValue: String = ""
override func viewDidLoad() {
super.viewDidLoad()
let defaults = UserDefaults.standard
strValue= defaults.string(forKey: "Gratitude")
}
and display in cellForItemAt like this:
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CollectionViewCell
cell.cellLabel.text = gratitudeArray[indexPath.row] + " " + strValue
return cell
}
If I understand your problem correctly, you want number of cells to increase by one (the value you extract out of UserDefaults). For that, you should append it some place which is outside collection view's data source methods (like viewDidLoad()) and then reload your collection view.
override func viewDidLoad() {
super.viewDidLoad()
let defaults = UserDefaults.standard
strValue = defaults.string(forKey: "Gratitude")
gratitudeArray.append(strValue)
self.collectionView.reloadData()
}
I solved the issue by creating an array in my alert controller to hold user input and then save that array to user defaults.
func presentAlert() {
let alertController = UIAlertController(title: "", message: "Create your own Gratitude:", preferredStyle: .alert)
let confirmAction = UIAlertAction(title: "Save", style: .default) { (_) in
if let field = alertController.textFields {
let textFieldArray = field as [UITextField]
let text = textFieldArray[0].text
let key = "Gratitude"
var myArray = UserDefaults.standard.array(forKey: key) ?? []
myArray.append(String(describing: text!))
UserDefaults.standard.set(myArray, forKey: key)
print(myArray)
} else {
print()
}
}
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) { (_) in }
alertController.addTextField { (textField) in
}
alertController.addAction(confirmAction)
alertController.addAction(cancelAction)
self.present(alertController, animated: true, completion: nil)
}