Issue with prepare for Segue and AccessoryButtonTappedForRowWith - swift3

After clicking on a accessoryButton in TableView, it will redirect me first to PrepareforSegue function and then to accessoryButtonTappedForRowWith Function which creating error in my code.
Please have a look into my code:
var indexPathAccessory: Int?
override func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) {
indexPathAccessory = indexPath.row
}
indexPathAccessory contains the value of row where Accessorybutton was clicked.
The second function is:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "Add" {
let nc = segue.destination as! UINavigatorController
controller = nc.topViewController as! AVC
//Comment 1
controller.name = span[indexPathAccessory]
//Comment 2
controller.delegate = self
}
}
I am getting error cause after clicking on accessorybutton, it is redirecting me to prepareforSegue function first and then to accessoryButtonTappedForRowWith and due to value of indexPathAccessory which still nil, I am getting this error
Will it be possible to jump first on accessoryButtonTappedForRowWith to get the value of indexPathAccessory and then to prepareForSegue?
if yes, problem will be solved.
I tried to add one test to return if indexPathAccessory is nil.
application run without error but not in a proper way:
I can see that controller.name is equal to "test", but when I am on AVC View controller, name became nil
Any advise?

One of the solution can be - remove the segue from the storyboard which causes the control going out of your hand and write the performSegue inside your accessoryButtonTappedForRowWith method.

Finally it worked by deleting the accessoryButtonTappedForRowWith function, and adding the following code to prepareforSegue functions:
if let indexPath = tableView.indexPath(for: sender as! UITableViewCell) {
controller.beamName = "Test"
indexSpanAccessory = indexPath.row
}

Related

Getting nil response while passing string to another view controller - Swift3

In FirstViewController i'm fetching the response from JSON and want to pass that fetched response to another view controller.Below is the code which i have used so far for parsing and passing the response.
FirstViewController
var fn:String! //globally declared variable
code i have tried for parsing in FirstViewController
do {
let detailsDictionary:NSDictionary = try JSONSerialization.jsonObject(with: data!, options:.allowFragments) as! Dictionary<String, AnyObject> as NSDictionary
print(detailsDictionary)
let details = detailsDictionary["Data"] as! [[String:AnyObject]]
print(details)
for dtl in details
{
self.fn = dtl["Father_Name"] as? String ?? "NA"
print(self.fn) //here i'm getting the exact value from JSON
}
}
}
SecondViewController
In SecondViewController there is a Label called profile_name and want to set that parsed string(fn) as Label's text. for that i declared another variable as global.
var pname:String!
below is the code i have used to fetch the value from FirstViewController.
viewDidLoad()
{
let othervc = FirstViewController()
self.pname = othervc.fn
self.profile_name.text = self.pname
}
Problem : I tried my best efforts to get the desired output but i'm getting nil response.
Please Help.
In Second ViewController
let strName:String!
In First ViewController
let strOne = "This is for testing"
let objstory = self.storyboard?.instantiateViewController(withIdentifier: "yout Secoond ViewController Storybord ID") as! YourSecondViewControllerName
objstory.strNam = strOne
self.navigationController?.pushViewController(objstory, animated: true)
Your updated code just won't work.
let othervc = FirstViewController()
creates a new instance of FirstViewController (not the one that got the JSON).
You should be handling it something like this:
In FirstViewController
let fn = dtl["Father_Name"] as? String ?? "NA"
let svc = SecondViewController() // Or maybe instantiate from Storyboard, or maybe you already have a reference to it
svc.pname = fn
present(svc, animated: true, completion: nil)
Then in SecondViewController
override func viewDidLoad() {
super.viewDidLoad()
profile_name.text = pname
}
I'd suggest you take some time out and re-read Apple's View Controller programming guide.
Original Answer
The problem you have here…
vcvalue.profile_name.text = fn
is that profile_name is nil as the view for the view controller hasn't been loaded at this point.
You should handle this by creating a property in LeftSideMenuViewController
var name: String?
Then set
vcvalue.name = fn
And then in LeftSideMenuViewController
override func viewDidLoad() {
super.viewDidLoad()
profile_name.text = name
}
Also, some basic tips…
Don't force unwrap (!) apart from IBOutlets. You may have to write a bit more code, but you will reduce crashes.
Make #IBOutlets private - this will prevent you accidentally assigning to them as you are now
If you're overriding any viewWill/DidDis/Appear methods, you must call super at some point.
You need to re-read the section on switch/case
So this…
let a = indexPath.row
switch(a)
{
case 0 :
if(a == 0)
{
return 45
}
break
etc
could just be…
switch indexPath.row {
case 0...4:
return 45
case 5:
return 50
default:
break
}

iOS - How to insert a new To-Do item in a single view controller?

I want to add a new To Do item when i press the add button,but i don't want to switch to another page.
press the add button to add a new row in the table view,input something and press the done button,it will save.
somebody suggests me to save the cells data to Model,but i don't know how to write this.
Who can help me?
import UIKit
import CoreData
class ToDoViewController: UIViewController {
var items: [NSManagedObject] = []
#IBOutlet weak var tableView: UITableView!
#IBAction func addItem(_ sender: UIBarButtonItem) {
//***How to write this code***
}
#IBAction func done(_ sender: UIBarButtonItem) {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext = appDelegate.persistentContainer.viewContext
let entity = NSEntityDescription.entity(forEntityName: "ToDo", in: managedContext)!
let item = NSManagedObject(entity: entity, insertInto: managedContext)
//***let list = the current textView's text
//how to get the textView's text and assign it to a value.***
item.setValue(list, forKeyPath: "summary")
do {
try managedContext.save()
items.append(item)
} catch let error as NSError {
print("Could not save.\(error),\(error.userInfo)")
}
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(UITableViewCell.self,forCellReuseIdentifier: "Cell")
}
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: "ToDo")
do {
items = try managedContext.fetch(fetchRequest)
} catch let error as NSError {
print("Could not fetch.\(error),\(error.userInfo)")
}
}
}
extension ToDoViewController: UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let item = items[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
let textView = UITextView(frame: CGRect(x: 0, y: 0, width: cell.frame.size.width, height: cell.frame.size.height))
cell.addSubview(textView)
textView.text = item.value(forKey: "summary") as? String
return cell
}
}
Ok so If my understanding is right you need a new row to be added if they create a new entry into your Core Data. So in your viewWillAppear you're doing a fetch. What I think you need is a:
var fetchResultController : NSFetchedResultsController<YourType>!
Then using this fetch controller you want to do the following when fetching:
private func GetToDoEntries(){
if let appDele = UIApplication.shared.deletgate as? AppDelegate{
let givenContext = appDele.persistantContainer.viewContex
let entryFetchRequest : NSFetchRequest<YourType> = YourType.fetchRequest()
let sortDescriptors = NSSortDescriptor(key: "yourEntrySortKey" , ascending: true)
entryFetchRequest.sortDescriptors = [sortDescriptors]
fetchResultController = NSFetchedResultsController(fetchRequest: entryFetchRequest, managedObjectContext: givenContext, sectionNameKeyPath: nil, cacheName: nil)
fetchResultController.delegate = self
do{
//Gets fetched data based on our criteria
try fetchResultController.performFetch()
if let fetchedEntries = fetchResultController.fetchedObjects{
items = fetchedEntries as? WhateverToCastTo
}
}catch{
print("Error when trying to find entries")
}
}
}
First I'm sorry but I've just written this here is stackOverflow. So what you're doing is using a fetch result controller instead of a traditional search. You are required to have the sort descriptor as well and you can try to get the results and cast them to your items or as a NSManagedObject.
Now we're not done though. Your script needs to inherit from some behaviour. At the top of your class
class ToDoViewController : UIViewController, NSFetchedResultsControllerDelegate
You need the delegate as you can see in the first block of code because we're assigning it. Now we're almost there. You just need some methods to update the table for you and these come with the delegate we just inherited from.
//Allows the fetch controller to update
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.beginUpdates()
}
//Allows new additions to be added to the table
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
switch type{
case .insert:
if let _newIndexPath = newIndexPath{
tableView.insertRows(at: [_newIndexPath], with: .fade)
}
case .update:
if let index = indexPath{
tableView.reloadRows(at: [index], with: .fade)
}
default:
budgetEntryTable.reloadData()
}
if let fetchedObjects = controller.fetchedObjects{
items = fetchedObjects as! [NSManagedObject (Or your cast type)]
budgetEntryTable.reloadData()
}
}
//Ends the table adding
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.endUpdates()
}
So there are 3 methods here. The first and second are very self explanatory. They begin and end the updates on your tableView. I'd also recommend that you change the name of your tableView to something other than "tableView" just for clarity.
The method in the middle uses a switch. My example is missing the "Move" and "Delete" options as I didn't required them in my project but you can add them to the switch statement.
The insert is checking the newIndexPath to see if there is one. If so then we add an array of the amount of rows required at the newIndexPath.
The update just checks the current index paths and then reloads the data on them incase you updated your data model.
I hope this is what you were looking for. Good luck! I'll try and help more if you need it but that should get you started.

UICollection is very slow in showing Image with Swift 3

I'm using UICollectionView to show the images in my app.
The problem is that it takes very slow to show images. After 50 seconds, the images in collection view appears. :(
When I find the solution in google, mostly they write the following codes. But it is not work for me.
cell.layer.shouldRasterize = true
cell.layer.rasterizationScale = UIScreen.main.scale
and
extension SeeAllCollectionView {
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
debugPrint("seeAllLIStCell Count \(assetsTable.count)")
return assetsTable.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "seeAllListCell", for: indexPath) as! SeeAllPhotoCell
let list = assetsTable[(indexPath as NSIndexPath).row]
var imageName: String? = (list.poster_image_url)
var image: UIImage? = (images_cache[imageName!])
if image != nil {
debugPrint("Yes Image")
cell.imageView.image = image
} else{
debugPrint("NO Image")
cell.imageView.image = nil
DispatchQueue.main.async(){
let url = NSURL(string: list.poster_image_url)
let data = NSData(contentsOf:url! as URL)
var image = UIImage(data: data as! Data)
DispatchQueue.main.async(execute: {() -> Void in
cell.movieTitle.text = list.name
cell.imageView?.image = image
})
self.images_cache[imageName!] = image
}
}
return cell
}
}
// MARK: - UICollectionViewDelegate
extension SeeAllCollectionView {
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
debugPrint("Selected")
let list = assetsTable[(indexPath as NSIndexPath).row]
debugPrint(list.poster_image_url)
debugPrint(list.name)
prefs.set(list.poster_image_url, forKey: "poster_image_url")
prefs.set(list.name, forKey: "name")
prefs.set(list.assets_id, forKey: "VIDEO_ID")
prefs.set(false, forKey: "FLAG")
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: "DetailsChannel") as UIViewController
self.present(vc, animated: true, completion: nil)
}
}
Here is my screenshot when I run the project. I got these many lines of codes when I run.
Please anyone help me how should I do?
I got the same console error when I am getting the data from the API call and reload the UITableView (as per my requirement). My issue is solved by using
DispatchQueue.global(qos: .background).async { // load data in back ground mode so that main thread can be safed.
let url = NSURL(string: list.poster_image_url)
let data = NSData(contentsOf:url! as URL)
var image = UIImage(data: data as! Data)
DispatchQueue.main.async(execute: {
cell.movieTitle.text = list.name
cell.imageView?.image = image
})
self.images_cache[imageName!] = image
}
Screen Shot of Error I got on my Console Before
You can give an attempt to SDWebimage All async Thread operations maintained well.
That can provide following advantages
Asynchronously download
Auto Purging Image Cache if memory warnings happen for the app
Image URL caching
Image Caching
Avoid Duplicate Downloads
You can directly use a single method in cell as below
[cell.storeImg sd_setImageWithURL:[NSURL URLWithString:strURL] placeholderImage:kDefaultImageForDisplay];
In your code following line should creates problem as that operation can happen on main thread
let data = NSData(contentsOf:url! as URL)
this line:
DispatchQueue.main.async()
upon downloading image is probably causing the error, it blocks the main thread and executes network requests on main (UI) queue, not to mention the fact that these requests are then performed serially (therefore slow). Try to change the snippet to:
DispatchQueue.global.async(qos: DispatchQoS.QoSClass.background){
let url = NSURL(string: list.poster_image_url)
let data = NSData(contentsOf:url! as URL)
var image = UIImage(data: data as! Data)
DispatchQueue.main.async(execute: {() -> Void in
where global stands for global queue default for such network operations.
EDIT: apart from using main queue for network calls there may be an issue with actually too many images loaded for rows which are not currently visible on the screen. If there are lot of them and connection is not-so-good you will end up with app pending downloads for onscreen cells. Consider lazy loading images only for onscreen cells - and cancelling downloads for rows which are not visible. There is a quite good tutorial (however for table view, but you can easily extend it for collection) in Swift on how to accomplish this.

segue sending array to destinationVC swift 3

I am trying to segue to a new viewcontroller in swift 3. I am also trying to send an array to the new viewcontroller, I am doing this programmatically as well. here is the code for my segue:
performSegue(withIdentifier: "AlbumSegue", sender:self) {
let destinationVC = segue.destination as! DestinationViewController
destinationVC.recievedArray = selectedAlbumArray
The error I get is "Extra Argument 'Sender' in Call"
So if I take out the sender part and my code becomes:
performSegue(withIdentifier: "AlbumSegue") {
let destinationVC = segue.destination as! DestinationViewController
destinationVC.recievedArray = selectedAlbumArray
}
I then get an error saying "use of unresolved Identifier 'segue'"
But I've found code examples like this:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let destinationVC = segue.destination as! DestinationViewController
destinationVC.receivedString = stringToPass
}
}
Basically both of these problems are in TEMPLATES for doing this so I don't know why either errors are occurring because according to answers on StackOverflow these aren't errors at all.

Nib must contain exactly one top level object which must be a UITableViewCell instance in swift

I'm using a custom CustomTableViewCell. When I try to run my code, I got this exception stack and I can't understand the source of the problem:
terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'invalid nib registered for identifier (ID) - nib must contain exactly one top level object which must be a UITableViewCell instance'
In CustomTableViewCell has two cells in one xib file, and i have given different identifiers
Some piece of code
override func viewDidLoad()
{
super.viewDidLoad()
let nib = UINib(nibName:"CustomTableViewCell", bundle:nil)
tableview.register(nib, forCellReuseIdentifier:"ID")
tableview.register(nib, forCellReuseIdentifier:"ID2")
}
after that
func tableView(_tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0
{
let cell = tableView.dequeueReusableCell(withIdentifier:"ID") as! CustomTableViewCell
cell.textLabel.text = "TEXT"
cell.detailTextLabel.text = "DETAIL TEXT"
return cell
}
else
{
let cell = tableView.dequeueReusableCell(withIdentifier:"ID2") as! CustomTableViewCell
cell.imageView.image = UIImage(named:"image.png")
return cell
}
}