Prepare for segue data not being loaded - swift

I have a carousel and each carousel item contains a unique tableview. When you tap the add button it takes you to a new view controller so that you can give the tableview a name and associate items to display in each cell. Now when I hit the save button it performs the segue back to the carousel's viewcontroller. My prepareforsegue looks like this:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let destinationVC = segue.destination as! BillSplittersViewController
let passedBill: NSManagedObject = bill as NSManagedObject
destinationVC.allBillSplitters = getUpdatedSplitters()
print(2)
print(destinationVC.allBillSplitters)
destinationVC.billName = billName
destinationVC.bill = passedBill
}
When the carousel view controller is displayed it doesn't show the added item in the carousel which loads from the allBillSplitters array. If I try reloading the carousel in viewdidload, viewwillappear or view willlayoutsubviews nothing changes. It does update if I do it in viewdidlayoutsubviews but you can see this happening which isn't great.
To debug I tried printing the array in prepareforsegue and then in viewdidload and it prints the array in prepareforsegue without the added item and the does the same thing in viewdidload -- but then it prints them again with the added item -- but I can't see it in the loaded view.
I'm using iCarousel if that helps. I am new to this and unsure what's going on so I don't know what code to use. I am using storyboards for both viewcontrollers and the segue is attached to the view and not the button itself. Any help would be great. Thanks!
More information:
#IBAction func saveButtonWasPressed() {
let managedContext = bill.managedObjectContext
if let splitter = splitter {
splitter.mutableSetValue(forKey: "items").removeAllObjects()
setBillSplitterValues(splitter)
setSelectedItemsToBillSplitter(splitter)
} else {
let entity = NSEntityDescription.entity(forEntityName: "BillSplitter", in: managedContext!)
let newBillSplitter = NSManagedObject(entity: entity!, insertInto: managedContext)
setBillSplitterValues(newBillSplitter)
setSelectedItemsToBillSplitter(newBillSplitter)
}
do {
try managedContext!.save()
}
catch let error as NSError {
print("Core Data save failed: \(error)")
}
self.performSegue(withIdentifier: "segueToBillSplitters", sender: self)
}
retrieving the new billSplitters:
func getUpdatedSplitters() -> [BillSplitter] {
var allBillSplitters = [BillSplitter]()
let managedContext = bill.managedObjectContext
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "BillSplitter")
let predicate = NSPredicate(format: "ANY bills == %#", bill)
fetchRequest.predicate = predicate
do {
let results =
try managedContext!.fetch(fetchRequest)
allBillSplitters = results as! [BillSplitter]
} catch let error as NSError {
print("Could not fetch \(error), \(error.userInfo)")
}
return allBillSplitters
}
So when pressing save, it either updates an existing 'billSplitter' to coredata or adds a new one, then calls performSegue. In prepareForSegue it then fetches all the 'billSplitters' from CoreData and passes them to the destination view controller.
I thought a NSManagedObjectContextDidSave observer might help if it called performSegue once the notification was received but nothing changed.
Update:
I have tried the following in my saveButtonWasPressed method. When the viewcontroller loads, it quickly shows the old carousel then flickers and shows the updated carousel. I'm not sure where to go from here.
#IBAction func saveButtonWasPressed() {
DispatchQueue.global(qos: .background).async { [weak weakSelf = self] in
let managedContext = weakSelf?.bill.managedObjectContext
if let splitter = weakSelf?.splitter {
splitter.mutableSetValue(forKey: "items").removeAllObjects()
weakSelf?.setBillSplitterValues(splitter)
weakSelf?.setSelectedItemsToBillSplitter(splitter)
} else {
let entity = NSEntityDescription.entity(forEntityName: "BillSplitter", in: managedContext!)
let newBillSplitter = NSManagedObject(entity: entity!, insertInto: managedContext)
weakSelf?.setBillSplitterValues(newBillSplitter)
weakSelf?.setSelectedItemsToBillSplitter(newBillSplitter)
}
do {
try managedContext!.save()
}
catch let error as NSError {
print("Core Data save failed: \(error)")
}
DispatchQueue.main.async { [weak weakSelf = self] in
guard let weakSelf = weakSelf else { return }
weakSelf.performSegue(withIdentifier: "segueToBillSplitters", sender: self)
}
}
}

I may not understand what you are asking exactly but my only input is that when I use prepare for segue I always use a segue identifier
var valueToPass
override func prepare(for segue: UIStoryboardSegue, sender: Any?){
if (segue.identifier == "yourSegue") {
let viewController = segue.destination as! ViewController
viewController.passedValue = valueToPass as String
}
Your declare your identifier in the storyboard, also make sure your segue is an action segue

Related

Having trouble with passing data from one ViewController to the next

I'm learning swift from couple months and one thing I keep screwing up is passing data between ViewControllers. When I PO the path of the forward looking variable from within the prepareForSegue method, the value is intact. But when the new ViewController actually appears and I checked its value, it is nil at that point. if anyone could point me in the right direction I'd be very appreciative.
class LoginViewController: UIViewController {
var user_ID:String = ""
//this below is within another method activated by button
Auth.auth().signIn(withEmail: email, password: password) { (result, error) in
if error != nil {
print ("there was an error signing in")
print (error!.localizedDescription)
return
}
else {
//go to home screen
let userUID = result?.user.uid
print (userUID)
self.user_ID = userUID
self.performSegue(withIdentifier: "MainSegue", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
//this is the destination VC
let viewController = segue.destination as! ViewController
viewController.user_ID = self.user_ID
//NOTE: if I break here and PO viewConroller.user_ID the value is intact
let homeViewController = (self.storyboard?.instantiateViewController(withIdentifier: "MainVC"))! as! ViewController
//let mainViewController = ViewController()
//mainViewController.user_ID = userUID
self.present(homeViewController, animated: true, completion: nil)
}
class ViewController: UIViewController {
var persons = [Person]()
let db = Firestore.firestore()
var user_ID:String = ""
//NOTE: WHEN `viewdidload` runs value of user_ID is nil
Phillip in the comments above provided the solution. The present ViewController was the problem. The segue already does that. Thank you!!!

In Xcode 11.4, I Cannot Hide A Button

So I am trying to make an Painting app that can store the data that user gives (such as name of the painting, artist of the painting, year of the painting and image of the painting) and shows in a table view. I have 2 view controllers, first one is called ViewController that has a table view for showing the data (only name of the painting for the table view cell) and the second one is called DetailedVC which is for entering and saving the data, also showing the details of the data. In the second view controller I added 3 text fields, 1 image view that enables the user to go the photo library and a save button. I wrote this in my DetailedVC script for when the user taps one of the elements from the table view in my ViewController:
override func viewDidLoad() {
super.viewDidLoad()
if chosenPainting != "" {
savebutton.isHidden = true //Trying to hide the save button here!!
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
let fetch = NSFetchRequest<NSFetchRequestResult>(entityName: "Paintings")
let idString = chosenPaintingId?.uuidString
fetch.predicate = NSPredicate(format: "id = %#", idString!)
fetch.returnsObjectsAsFaults = false
do {
let results = try context.fetch(fetch)
if results.count > 0 {
for result in results as! [NSManagedObject] {
if let name = result.value(forKey: "name") as? String {
nameText.text = name
}
if let artist = result.value(forKey: "artist") as? String {
artistText.text = artist
}
if let year = result.value(forKey: "year") as? Int {
yearText.text = String(year)
}
if let imageData = result.value(forKey: "image") as? Data {
let imageD = UIImage(data: imageData)
imageView.image = imageD
}
}
}
} catch {
print("error")
}
} else {
savebutton.isEnabled = false
nameText.text = ""
artistText.text = ""
yearText.text = ""
}
I get the information from my ViewController like this:
#objc func AddButtonTapped(){
selectedPainting = ""
performSegue(withIdentifier: "toDetailedVC", sender: self)
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
selectedPaintingId = ids[indexPath.row]
selectedPainting = names[indexPath.row]
performSegue(withIdentifier: "toDetailedVC", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "toDetailedVC" {
let destination = segue.destination as! DetailedVC
destination.chosenPainting = selectedPainting
destination.chosenPaintingId = selectedPaintingId
}
}
It works just fine below the savebutton.isHidden = true. I don't understand why the button is not disappearing. Everything else works correctly, text field's texts turn into the information that user gave but, the button is standing still right in the bottom. And even the savebutton.isEnabled = false is working. I thought there must be a problem with connection between my script and my storyboard so I deleted and did it again, but it didn't work again. I must have doing something wrong, can you help me about this?

Enabling 3D Touch preview for TableView

I've got TableViewController that segues to UIViewController where WKWebView is located. Every TableView cell points to particular local html file, which is then loaded in WebView.
I can simply check the Peek & Pop - Preview & Commit Segues in Attributes Inspector for that segue, but the preview will show always the same html file, rather than different one for each table cell selected.
What is the proper way to implement 3D Touch, or how to somehow prepare the html content for preview in the first TableViewController?
.xcodeproj on my github. More info on demand.
Thanks for help!
Ok, i saw your code and you aren't implement 3D touch, first of all you need to check if the 3DTouch is available in your viewDidLoad() method:
if( UIApplication.shared.keyWindow?.traitCollection.forceTouchCapability == .available{
registerForPreviewing(with: self, sourceView: view)
}
and then, implement the delegate of Preview UIViewControllerPreviewingDelegate, you are talking about pop a viewcontroller with 3dtouch, implement this function in delegate
extension:YourViewController:UIViewControllerPreviewingDelegate{
func previewingContext(previewingContext: UIViewControllerPreviewing, commitViewController viewControllerToCommit: UIViewController) {
//Here's where you commit (pop)
//You are trying to show into tableview, the next code makes this
//For TableView
guard let indexPath = tableView?.indexPathForItem(at:location) else {
return nil }
guard let cell2 = tableview.cellForRow(at: indexPath) else { return nil }
guard let popVC = storyboard?.instantiateViewController(withIdentifier: "popVC") as? YourClassViewControllerToShow else { return nil }
let html = htmls[indexPath.row]
popVC.html = html
show(popVC, sender: self)
}
}
and i switch the code from Detail.swift viewDidLoads() to viewWillAppear()
content.uiDelegate = self
content.navigationDelegate = self
let url = Bundle.main.url(forResource: result?.id, withExtension: "html", subdirectory: "Library")!
let request = URLRequest(url: url)
content.loadFileURL(url, allowingReadAccessTo: url)
content.load(request)
with this last, you are insuring the webview always refresh when appear.
There are no selected cells when peeking, therefore you cannot just ask the table about the selected cell. Instead, you have to handle the sender parameter, which is the UITableViewCell:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "transporter" {
guard
let cell = sender as? UITableViewCell,
let path = table.indexPath(for: cell)
else {
return
}
let selected = filtering() ? filter[path.section].rows[path.row] : list[path.section].rows[path.row]
let controller = (segue.destination as! Detail)
controller.result = selected
}
}

Swift - Unwind Segue Pass Data Confusion

I've tried passing data backward from my unwind segue in a number of ways. It seems like the data is not getting sent or its getting sent after viewDidLoad() so the label I'm trying to set isn't getting updated. The unwind segue is working, and below I use prepare for segue with some success to change the title of the previous view controller to 'new title', but the last line isn't setting nbaRotoHome.player to 'new player name'.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "BuyStatsTapPager" {
let nav = segue.destination as! UINavigationController
let buyStatsTapPager = nav.viewControllers[0] as! BuyStatsTabPager
buyStatsTapPager.selectedPlayerBuyStats = selectedPlayer
buyStatsTapPager.buyStatsRef = self
}
if segue.identifier == "unwindToViewController1" {
var viewControllers: [UIViewController] = mainNavigationController.viewControllers as [UIViewController];
if(viewControllers.count == 3){
viewControllers.remove(at: viewControllers.count-2)
mainNavigationController?.viewControllers = viewControllers
}
let enteredContestViewController = viewControllers[viewControllers.count-1]
enteredContestViewController.title = "new title"
self.presentingViewController?.dismiss(animated: true, completion: nil)
let nbaRotoHome = segue.destination as! NBARotoHome
nbaRotoHome.player = "new player name"
}
Back in my previous view controller I have
#IBAction func prepareForUnwind(segue: UIStoryboardSegue) {
}
And after looking at this question
Passing data with unwind segue
I've also tried getting the data this way in the previous view controller
#IBAction func prepareForUnwind(segue: UIStoryboardSegue) {
if let sourceViewController = segue as? BuyStats {
playerNameLabel.text = sourceViewController.playerName
}
}
If I need to add more detail to what I'm trying to do please ask and I will edit. I wanted to ask the question but I am having trouble formulating.
It seems like the data is not getting sent or its getting sent after viewDidLoad() so the label I'm trying to set isn't getting updated.
In an unwind segue you are returning to an already created viewController, so viewDidLoad happened ages ago before you segued to the other viewController.
If you're using segues, you should not be mucking with the array of viewControllers in the navigationController or calling dismiss. The unwind segue will do all of that. Just get the destination in prepare(for:sender:) and set the data:
if segue.identifier == "unwindToViewController1" {
let nbaRotoHome = segue.destination as! NBARotoHome
nbaRotoHome.player = "new player name"
}
or in your prepareForUnwind get the source and read the data:
In this line you are missing .source. Change:
if let sourceViewController = segue as? BuyStats
to:
if let sourceViewController = segue.source as? BuyStats

Passing same object context 2 two separate view controllers

I have a managedObject that is being passed from 1 view controller to another the first pass works fine but when I try to pass the next object after the relationship has been set it doesn't send anything and comes back as either nil or if I try to use other methods comes back with a syntax error. The code I am using for the view controllers is as follows
View Controller 1, The first object set:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let identifier = segue.identifier {
switch identifier {
case "popOver":
if let VC = segue.destinationViewController as? ClassDeckNameViewController
{
if let ppc = VC.popoverPresentationController {
VC.modalPresentationStyle = UIModalPresentationStyle.Popover
ppc.permittedArrowDirections = UIPopoverArrowDirection.Any
ppc.delegate = self
}
VC.classSave = (sender as! ClassSelection)
}
default: break
}
}
}
#IBAction func buttonPriest(sender: AnyObject) {
let entity = NSEntityDescription.entityForName("ClassSelection", inManagedObjectContext: classMOC!)
let newObject = ClassSelection(entity: entity!,insertIntoManagedObjectContext: classMOC)
newObject.classname = "Priest"
var error: NSError?
if let err = error {
println(err)
} else {
classMOC?.save(&error)
self.performSegueWithIdentifier("popOver", sender: newObject)
}
}
This passes the object without problem to the second view controller but this is the one that won't pass any further to the final presenting controller offering the user the final selections for their "Deck":
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showCardSelection" {
let detailVC: CardSelectionViewController = segue.destinationViewController as! CardSelectionViewController
detailVC.passedDeckObject = (sender as! Deck)
}
}
#IBAction func enterButton(sender: AnyObject) {
let entityDescription = NSEntityDescription.entityForName("Deck",inManagedObjectContext: managedObjectContext!)
let storeDeck = Deck(entity: entityDescription!,insertIntoManagedObjectContext: managedObjectContext)
storeDeck.deckname = usersDeckName.text
storeDeck.classSelected = classSave!
var error: NSError?
managedObjectContext?.save(&error)
if let err = error {
status.text = err.localizedFailureReason
} else {
usersDeckName.text = ""
status.text = "Deck Saved"
self.performSegueWithIdentifier("showCardSelection", sender: storeDeck)
}
}
I made passedDeckObject a variable of type Deck? in the final view controller to set the final relationship methods I know I am doing something wrong but I am unsure what! Any help with this would be amazing!
This looks to be a misconfiguration issue where the segue is being triggered directly in the storyboard rather than calling your code. As such the sender is a button rather than the new entity instance you're expecting.
To fix, disconnect the segue in the storyboard and connect (if it isn't already) the button to your action method in the view controller.