I'm building a weather app that is hosted on a public repo here.
The most important files for this issue are the PageView and the RepresentedPageViewController.
I've created a UIPageViewController that interfaces with SwiftUI via UIViewControllerRepresentable. It allows a user to swipe through different cities and see each city's weather data, much like Apple's Weather app. When the makeUIViewController method of my page view controller is called I set its view controllers (there are 3 in this case to begin with, where each represents a city):
pageViewController.setViewControllers([controllers[0]],
direction: .forward,
animated: false)
This works fine and I'm able to navigate (swipe) between the different pages.
In the app's search menu, a user can then tap on a new city that they want to get the weather for. This adds the new city to the app's datasource of cities that the page view controller uses to create a page for each city.
Because the data object that holds the cities is stateful, the UI is recomputed when that object is set (when a user adds a new city). This triggers the updateUIViewController method in the page view controller. In this method, I reset the page view controller's viewcontrollers (there are now 4 because the user has added a new city):
func updateUIViewController(_ uiViewController: UIPageViewController, context: UIViewControllerRepresentableContext<RepresentedPageViewController>) {
// Check that the view controllers of the page view controller need to be reset.
// This is true when a user has added a new city because we'll create a new
// view controller to be added to the page view controller. We perform this check because
// we don't want to reset the viewcontrollers in all cases where the updateUIViewController method is called.
if shouldResetControllers {
uiViewController.setViewControllers([controllers[0]],
direction: .forward,
animated: false)
shouldResetControllers = false
}
}
My issue is that once this is done, the user is only able to see the first city of the viewcontrollers. The page view controller is still there because the user is still able to swipe, but there is only one city (the first one). I've created a screen recording of the result which you can view here.
think the problem stays in the Coordinator:
when you update controllers in pageviewcontroller, the parent property of Coordinator remains the same (and because it is a struct, all his properties). So you can simply add this line of code in your updateUIViewController method:
context.coordinator.parent = self
also, remember that animation of pageViewController.setViewControllers will occur, so you have to set animated to false, or handle it properly.
There are many other ways to solve this (I wrote the most intuitive solution),
the important thing is to know where the error comes from.
It seems like this is a SwiftUI bug. I had a similar problem where a UI update would reload the PageViewController with a single ViewController.
Adding an id to the PageViewController, described in "Updating an #Environment to pass data to PageViewController in SwiftUI results in loss of swiping between ViewControllers", seems to work.
Related
Some UIViewControllers do not seem to get deallocated.
What's the best way to list and identify all live (non-deallocated) UIViewControllers?
Run app in debugger and use "Debug memory graph" button and see the list of the view controllers in the panel on the left. If you happened to follow the convention of including ViewController in the name of your view controllers (e.g. MainViewController, DetailsViewController, etc.), you can filter the list of objects listed in the left panel by typing ViewController in the "filter" text box at the bottom of the left panel:
In this example, I also clicked on my third view controller, and I can see it was presented by the second one, which was presented by the first one.
The other approach is to use the "view debugger" , but that only shows the view controllers that are currently present in the active view controller hierarchy and may not show view controllers whose views are not currently visible because the view controller presented another view controller modally.
In addition to Rob's answer, if you want to see them initialized and deinitialized in real time you can print to console.
class Random32ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
print("32 did load")
}
deinit {
print("32 did deinit")
}
}
You can do this method on all class types beyond just view controllers.
I am creating a notecards app and I need to be able to make a new notecard as many times as I want. I want to do this so the user is not limited to an amount of note cards. I need to know how I would create a new view controller (or note card) if they want. So they creating page can be the same but the new note card cant be the same view as the last note card otherwise it wont be the new one they created. To get to the point I am trying to create a new view for a new notecard that was created.
Think of a scene in a storyboard as a template. When you invoke a view controller from a storyboard, you get a new copy.
So say your app has a master view controller with a "create note card" button on it. You could connect that button to an IBAction that instantiates and displays a new copy of your note card view controller. The IBAction method might look like this:
#IBAction func createNoteCard(UIButton sender)
{
//Create a new instance of a Note card view controller
let newCard =
self.storyboard.instantiateViewControllerWithIdentifier("NoteCard")
//Put your code to configure the view controller here.
//Display the new note card view controller to the screen
self.presentViewController(newCard, animated: true)
}
It is very common to have one design used multiple times. I think it's more logical to create one viewController, and pass it data based on which notecard is selected, or a blank dataset when a new card is "created". This is a common design for use with a UITableView (listing your notecards), which when clicked on, opens the particular card selected. The detail (notecard) view is always the same view, but you pass it different data.
I am wondering what is the best practice to keep some of the UI elements in place when going forward/backward between UIViewController for example if I am using UINavigationController.
To be specific. I am making an app that has several similar view controllers (they can be instances of one main view controller). Then user clicks the next button and goes to the next page; or swipe back to go to the previous page. I have a progress bar on top and one or more buttons on bottom that I wish to keep static in place while the rest of the content are changing with an animation (a simple push might work).
Now my question is, if is it better put the content inside a container view? or to implement custom transition to keep those items in place while moving the rest?
Here is an image of the concept:
In the navigation controller delegate you can implement navigationController(_ navigationController:, animationControllerFor:, from:, to:) to return a custom UIViewControllerAnimatedTransitioning object. If you go this route you will have to implement the whole animation yourself though.
If you want to keep the basic animations from UINavigationController and just keep your elements steady you can go another route. In your view controller implement viewWillAppear: and/or viewWillDisappear:. In there you can get the transitionCoordinator and call animate(alongsideTransition:, completion:) on that. With that you can run your custom animations in parallel to the system provided animations.
To keep fixed elements you add another copy of the fixed elements to the container view you can get from the context object that is passed to your block. In the completion block you then can remove it again.
Sounds complicated, but it actually is rather easy if you look at the code:
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
let fixedViewCopy = UIView(...)
fixedViewCopy.frame = self.fixedView.frame
transitionCoordinator?.animate(
alongsideTransition: { context in
context.containerView.addSubview(fixedViewCopy)
},
completion: { _ in
fixedViewCopy.removeFromSuperview()
}
)
}
I'm trying to do something which seems simple, but I am having difficulty. I have a tab bar application with a setting page which is a table view. The user can change the settings by selecting a row, which pushes a new table view with new setting options. When the user selects a new setting, I send the information back to the main table view and populate the table view with the new setting.
The problem : I also have a SAVE button. If the user does not save the new settings I want to discard them. If the user selects a new tab (without saving settings) and then selects the settings tab, the last data the user entered is still in the tableView. I understand this is because viewdidload is not called again after the view has been created.
Basically I want logic like this :
If segue was initiated by clicking a tab bar icon : load the table view using dataModel.
Else if the previous screen was the data entry screen : Load the table view using user selected data.
If I wasn't using a tab bar controller I would do this by loading the dataModel using viewdidload and loading the tableview using delegate methods. The problem is I can't use viewdidload. I also can't use viewwillappear because it is called both when opening the screen from the table view and when popping the entry view controller off the stack.
I tried to set up the delegate method for the Tab Bar Controller in AppDelegate
func tabBarController(tabBarController: UITabBarController, didSelectViewController viewController: UIViewController) {
if (tabBarController.selectedIndex == 2)
{ let navController = viewController as! UINavigationController
let settingsTableViewController = navController.viewControllers[0] as! SettingsTableViewController
settingsTableViewController.loadFromDataModel = true
println("In did select view controller in app delegate")
}
}
And if loadFromDataModel = true then I load from the data Model in the Settings Table View Controller. Here is the problem - this only works if I go back and forth from the tabs twice. So weird. I put in println statements and it seems like execution is happening in the following sequence:
ViewWillAppear is being called in SettingsTableViewController
tabBarController:didSelectViewController is being called from the
AppDelegate (and the variable loadFromDataModel is updated - but it is too late because viewwillappear has already been called)
In Summary : Is this the best way to determine if the segue came from the tab bar? Why is viewwillappear on my SettingsTableViewController being called before the delegate method? Any suggestions on how to load the data from the data model each time the user selects the tab via the tab bar. Am I missing some obvious method? Thanks for any help!
By the time didSelectViewController is called, it's too late to do what you are looking to do. As you can tell by the viewDWillAppear delegate firing. Try moving your code to the shouldSelectViewController delegate of the tabBarController That should be early enough in the process to set your data.
In my page based application i want to perform the operation next page on click of button?Is it possible to to go next page using button in page based application?Pls help me to solve this issue
Edit: My application like story book im showing set of images in pages while turning next page, i want to show the nxt page by click
In your app the class conforms to the UIPageViewControllerDataSource protocol, make one object of this class in root view controller suppose ModelController then the object is _modelController.
Then in root view controller just put two UIButton connect them with appropriate IBAction methods suppose for previous button previousClick: and for next button nextClick:.
Now the code for each method is as follow:
-(IBAction)previousClick:(id)sender { [_modelController pageViewController:self.pageViewController viewControllerBeforeViewController:_dataViewController]; }
-(IBAction)nextClick:(id)sender { [_modelController pageViewController:self.pageViewController viewControllerAfterViewController:_dataViewController]; }
Here I suppose that the pageViewController is the one object of type UIPageViewController and is one of the declared and synthesized variable of root view controller, and also same for the _dataViewController which is one UIViewController contains one UIImageView to present the images on the root view.
EDIT
This also can be done with the using the DataViewController for that all the logic have to move to that view controller and all so the references must be needed for the UIPageViewController and ModelController.