When transitioning from the details tab to the tags tab, the table view is not loading. I know because in the view did load I had it print a comment but the comment and the rest of the code is running or loading. Even though the table view itself is appearing. Sorry if this is too vague , I'm not sure what I need to provide so please let me know if you need more info to help me
When you click on a tab it's loaded and maintained in memory. It doesn't require to be reloaded. If you want to load the data again move the data loading code to viewDidAppear like this:
class ViewController: UIViewController {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
loadData()
}
func loadData() {
// Code to load data
}
}
Where you get data from Api reload your tableView tableview.reloadData() there on Main thread using DisaptchQueue.main.async {}
// got response from Api
DisaptchQueue.main.async {
tableView.reloadData()
}
Related
Xcode 11.0
Swift 5.1
I'm working on a "simple" app using Core Data.
The single Core Data entity is named Topic and comprises Title (String), Details (String) and isFavorite (Bool)
I'm using a custom UITabBarController with 3 tabs - Random Topic, Favorite Topics, and All Topics
Favorite Topics and All Topics are UITableViews sharing the same subClass, TopicsViewController.
Using custom TopicTabBarController I set a property in TopicsViewController, based on the tab, that determines whether or not a predicate is used with the FetchRequest for the two tabs sharing this controller.
This works as expected, even with FRC cache (I'm very new to Swift!)
If a topic is favorited on any view, the tableViews in both Favorite Topics and All Topics update to reflect that change. This is precisely how I want it.
The trouble is I'm getting a warning about the tableView being updated while not visible:
UITableView was told to layout its visible cells and other contents without being in the view hierarchy (the table view or one of its superviews has not been added to a window).
I set a symbolic breakpoint at UITableViewAlertForLayoutOutsideViewHierarchy but making sense of that is a bit above my head at the moment.
I tried returning early from the FRC Delegate methods if the view wasn't loaded but that was clearly not the solution. Like I said, this is all new to me.
I thought setting the FRC Delegate to nil and back to self as suggested in this post would help, but that only prevents the managedObjectContext from saving (at least that's what I'm seeing with Navicat). I can step through the code and see that the proper instances of the controller class are being manipulated based on the custom property I set for the predicate.
Here's the related code for that part:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.fetchedResultsController.delegate = self
self.performFetch()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
self.fetchedResultsController.delegate = nil
}
// MARK:- Helper methods
func performFetch() {
do {
try fetchedResultsController.performFetch()
} catch {
fatalCoreDataError(error)
}
}
Here's where the entity is saved, which is not happening with the above code in place:
#objc func toggleFavorite(_ sender: UIButton) {
let buttonPosition = sender.convert(sender.bounds.origin, to: tableView)
if let indexPath = tableView.indexPathForRow(at: buttonPosition) {
let topic = fetchedResultsController.object(at: indexPath)
topic.isFavorite = !topic.isFavorite
try! managedContext.save()
}
}
Here's a Gist with more details. I find it much easier to read this way.
I also just came across this post on the Apple Dev forum. Looks like the same issue, unresolved.
I honestly don't know if that's too much or too little information. Was really hoping I'd stumble on the solution while trying to explain the problem.
Thanks for your time.
For what it's worth, I was getting the same warning and I was able to fix it by doing something very similar to what you tried. Basically the only difference is the tableView.reloadData().
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
fetchedResultsController.delegate = self
tableView.reloadData()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
fetchedResultsController.delegate = nil
}
... though I would think that performing a fetch would work just the same?
In my HomeViewController have four section:
Banner
Popular Product
New Product
Old Product
All are .xib files and they are working fine.
Now I want to add ActivityIndicator in my HomeViewController
So now I want to show ActivityIndicator on HomeViewController until all the .xib's file not fully loaded in HomeViewController as .xib's ActivityIndicator should hide.
Now, the problem for me is that how can I get the confirmation about .xib's loaded to HomeViewController or not?
As a pretty simple direct solution, you could follow the delegation approach. So, when "Popular Product" View complete the needed process, you should fire a delegate method which will be handled by the view controller.
Example:
consider that in PopularProduct class you are implementing a method called doSomething() which need to get called and finish its work to hide the activity indicator from the view controller and it should send Data instance to the view controller. You could do it like this:
protocol PopularProductDelegate: class {
func popularProduct(popularProduct: PopularProduct, doSomethingDidPerformWith data: Data)
}
class PopularProduct: UIView {
// ...
weak var delegate: PopularProductDelegate?
func doSomething() {
// consider that there is much work to be done here
// which generates a data instance:
let data = Data(base64Encoded: "...")!
// therefore:
delegate?.popularProduct(popularProduct: self, doSomethingDidPerformWith: data)
}
// ...
}
Therefore, in ViewController:
class ViewController: UIViewController {
// ...
var popularProduct: PopularProduct!
override func viewDidLoad() {
super.viewDidLoad()
// show indicator
popularProduct.doSomething()
}
// ...
}
extension ViewController: PopularProductDelegate {
func popularProduct(popularProduct: PopularProduct, doSomethingDidPerformWith data: Data) {
print(data)
// hide indicator
}
}
Furthermore, you could check my answer for better understanding for delegates.
I couldn't find the answer to this probably because I'm not really sure what I'm looking for since i just started programing a few weeks ago.
My storyboard entry point requires data that I get from an asynchronous session and JSON parse. Then once it gets the data it stores it to NSUserDefaults so it doesn't have to make the async call again and the app can access that data anytime.
I put my async call in the viewdidload of the storyboard entry point because as far as I know thats where the app starts. The issue is that the data isn't showing up until the app is started for a second time.
The data I'm getting from the async call only changes once every month so its not necessarily time sensitive.
How can I delay the app from getting to the storyboard entry point until the async call is finished?
Is that even the right way to go about it?
Should I switch to a synchronous call?
What if I changed the storyboard entry point to a view controller that looked like the app was loading and then when the async call finished, use a completion handler to perform segue to the view controller that requires the asynchronous call to finish?
Thanks Leo that worked.
Storing a variable when the app is first installed
I did two things here. First I created a new view controller that would execute the async call and segue to my main view controller when it finished. Then I detected if it was the first launch or not by using the above linked solution. Both of those together worked.
import Foundation
import UIKit
class FirstLoad: UIViewController {
var installedDate: NSDate? {
get {
return NSUserDefaults().objectForKey("installedDateKey") as? NSDate
}
set {
NSUserDefaults().setObject(newValue, forKey: "installedDateKey")
}
}
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewDidAppear(animated: Bool) {
firstLoad()
}
func firstLoad() {
if installedDate == nil {
installedDate = NSDate()
parseData(heroesDataProject) { heroesArrayFromParse in //this function gets my json and the following code is executed after completion
saveToDefaults("heroesOriginal")
print("First Run")
self.performSegueWithIdentifier("firstLoadToHomeMenu", sender: nil)
}
} else {
print("Not first run, installd on \(installedDate!)")
loadFromDefaults(userProfile)
performSegueWithIdentifier("firstLoadToHomeMenu", sender: nil)
}
}
}
I am trying to program a little game just to apply the concepts I learned in this course myself. When the game opens up, I would like for a custom modal view to tell the user how to play. Likewise, when they lose, I want to present a results page, which would be inside an if statement. I've searched all over the internet, and I can't find a way to display these views without an error. All that this video shows is how to show a display a view when a button is pressed; how do I display a custom modal view on command in code? (I am very new to Swift, so try to put it in layman's terms.) Thanks!
import UIKit
var numberValue = 0
let randomInt = getRandomNumber()
class ViewController: UIViewController {
#IBOutlet weak var buttonLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
let vc = self.storyboard?.instantiateViewControllerWithIdentifier("HowToPlayView") as! HowToPlay
self.presentViewController(vc, animated: true, completion: nil)
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func buttonPressed() {
numberValue += 1
updateButtonValue(buttonLabel)
}
}
#IBAction func buttonPressed() {
numberValue += 1
updateButtonValue(buttonLabel)
}
}
That's my view controller. Getting error: Thread 1: EXC_BAD_ACCESS
My view in interface and connections
enter image description here
Ok, let's try a different approach to solve this issue. Above your ViewController you should see three different buttons like this:
Click on the first button and Ctrl + Drag to the view that you want to display and choose display modally. After that is done you should see this arrow appear:
This is called the segue from the first view to the second view and you can call this in your code to transition from one view to the next, but first we have to give our segue an identifier. You can do this by clicking on the arrow and a window should come up like this:
As you can see you will have to fill out the identifier field, and in my case I just named it "myAwesomeSegue".
Lastly, in the first ViewController run this code when you need to present the next view controller:
performSegueWithIdentifier("myAwesomeSegue", sender: nil)
Please note that if you use this method just delete the previous code you had before as this is a brand new approach (so basically delete this code):
let vc = self.storyboard?.instantiateViewControllerWithIdentifier("HowToPlayView") as! HowToPlay
self.presentViewController(vc, animated: true, completion: nil)
Final code for the firstViewController:
I am working on an app where it starts out at a tableViewController which loads and displays data stored in a Realm database. I have it so I can create a new entry in my Realm database in a separate scene and the save button unwind segues back to the initial tableView.
I current have it so the tableViewController will reload the tableView on pull down (something I Learned here, second answer down) but I would be better if the tableView would reload its self automatically upon unwind, displaying all the data in my database, including my new entry. Could someone please direct me to a tutorial that will teach me how this is done.
Additional info: My app is embedded in a navigation controller. The save button is located the bottom tool bar.
You can use NSNotification for that.
First of all in your tableViewController add this in your viewDidLoad method:
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "refreshTable:", name: "refresh", object: nil)
}
And this will call one method from your class:
func refreshTable(notification: NSNotification) {
println("Received Notification")
tableView.reloadData() //reload your tableview here
}
So add this method too.
Now in your next view controller where you add new data into data base add this in you unWindSegue function:
NSNotificationCenter.defaultCenter().postNotificationName("refresh", object: nil, userInfo: nil)
Hope it will help
Try reloading your tabledata in viewWillAppear in initial (tableview)controller.
override func viewWillAppear(animated: Bool) {
self.tableView.reloadData()
}
Or call again the function through which you are loading your data from Realm like
override func viewWillAppear(animated: Bool) {
getMyData() //or whatever your function name is
}
If you are using storyboard unwind segue, try using
func unwindSegue(segue:UIStoryboardSegue) {
if segue.identifier == "identifier" {
getData()
self.tableView.reloaddata()
}
}