Unbalanced calls to begin/end appearance transitions in UISplitViewController - uinavigationcontroller

My UISplitViewController basically works like a charm except that there is an annoying error message displayed when transitioning the first time (first time only!) from the master table view to the detail view.
Unbalanced calls to begin/end appearance transitions for <UINavigationController: 0x160015600>.
Both the master and the detail view controller are embedded in a UINavigationController. However, the error only occurs when setting the following (which is necessary for logic behavior on the iPhone):
class MySplitViewController: UISplitViewController, UISplitViewControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
}
func splitViewController(splitViewController: UISplitViewController, collapseSecondaryViewController secondaryViewController: UIViewController, ontoPrimaryViewController primaryViewController: UIViewController) -> Bool {
return true
}
}
It would be great if anyone could provide a solution to this issue, thanks in advance.
BTW: the split view controller was set up in the storyboard
Presenting the detail view controller is done in the tableView:didSelectRowAtIndexPath: method like this:
if let detailViewController = delegate as? DetailViewController {
detailViewController.navigationItem.leftItemsSupplementBackButton = true
detailViewController.navigationItem.leftBarButtonItem = splitViewController!.displayModeButtonItem()
splitViewController!.showDetailViewController(detailViewController.navigationController!, sender: self)
}

Most probably, your first transition from master (UITableView in UIViewController?) to detail (UIViewController) view in your UISplitViewController starts before the active/current view has finished displaying itself.
A possible reason for this is that you are possibly trying to present the first "instance" of the detail view in the viewDidLoad() method of you master UIViewController? In such a case, you app might try to present the detail view prior to master view finished appearing. Note the difference here between view did load a view and view did appear:
override func viewDidLoad()
Description:
Called after the controller's view is loaded into memory.
This method is called after the view controller has loaded its view
hierarchy into memory.
override func viewDidAppear(animated: Bool)
Description:
Notifies the view controller that its view was added to a view
hierarchy. You can override this method to perform additional tasks
associated with presenting the view.
Now, as you question doesn't show how you load your initial detail view, the following advice is maybe already heeded by yourself, but anyway: if your detail view is presented from the viewDidLoad(), try to move this to the viewDidAppear() method:
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(true)
// present/load detail view here instead
}

This might be too late an answer, but anyways, I solved this using perform segue instead of showDetailViewController

Related

UICollectionViewCell custom subview doesn't receive focus

I created custom Poster view so it can be reused in multiple collection view cells (just like TVPosterView in TVUIKit). I add it directly to cell content view with all needed constraints.
The problem is when cell is focused this subview doesn't receive focus update (didUpdateFocus..) so I cannot customize it's focused / unfocused constraints etc. It's odd btw that image view inside is getting floating effect.
In case if I specify cell's preferredFocusEnvironments to return [self.posterView] + super. preferredFocusEnvironments, UI behaves as expected, but the collection view delegate method didSelect not called!
Thanks in advance for any help!
Seems didUpdateFocus not called on all subviews for the focused cell and it's system design. From docs:
After the focus is updated to a new view, the focus engine calls this
method on all focus environments that contain either the previously
focused view, the next focused view, or both, in ascending order. You
should override this method to update your app’s state in response to
changes in focus. Use the provided animation coordinator to animate
changes in visual appearance related to the update. For more
information on animation coordinators, see
UIFocusAnimationCoordinator.
Note: So it means didUpdateFocus will be called first on UICollectionViewCell, than on UIViewController subclasses, in ascending order. For subviews you need to manually register customDidUpdateFocus method that will be triggered in notification update. E.g. to update it's appearance we can use notifications (tvOS 11+), please see the example below.
func customDidUpdateFocus(isFocused: Bool, with coordinator: UIFocusAnimationCoordinator) { /* Custom logic to customize appearance */ }
// Register observer
observerToken = NotificationCenter.default.addObserver(forName: UIFocusSystem.didUpdateNotification, object: nil, queue: .main) { [weak self] (note) in
guard let self = self else { return }
guard let context = note.userInfo?[UIFocusSystem.focusUpdateContextUserInfoKey] as? UIFocusUpdateContext else { return }
guard let coordinator = note.userInfo?[UIFocusSystem.animationCoordinatorUserInfoKey] as? UIFocusAnimationCoordinator else { return }
if let prev = context.previouslyFocusedView, self.isDescendant(of: prev) {
self.didUpdateFocus(isFocused: false, with: coordinator)
} else if let next = context.nextFocusedView, self.isDescendant(of: next) {
self.didUpdateFocus(isFocused: true, with: coordinator)
}
}

Navigation issue in Xamarin Forms with same page type multiple time

Relatively new to Xamarin, hitting an issue with PushAsync and navigation I can't figure out.
I have a main navigation page, and then a "MyContentPage" that is responsible for rendering a dynamic list based on a supplied id. When the user clicks on a list item they go to a next (newed up) "MyContentPage" (same class) with a different id. Basically a recursive page hierarchy based on a local db.
Problem is that navigation seems to quickly get messed up in some way I can't work out. The pages get swapped around, or get lost. Navigating back to root, if I click back down again, it skips to a page that is further down etc.
So basically the one page apart from the main page (which has multiple navigationpages in tabs - though I only use one tab at this point) binds its controls to this function:
public async Task NavigateToContent(int contentId)
{
await ((Application.Current.MainPage) as TabbedPage)?.CurrentPage.Navigation.PushAsync(new MyContentPage(contentId));
}
The above is then used recursively. Ie. Similar controls bind to the same function until there are no further pages to click down to.
The MyContentPage constructor loads the model:
public MyContentPage(int id)
{
InitializeComponent();
_id = id;
BindingContext = viewModel = new ContentPageViewModel(id);
}
What is the issue here?
From what you mentioned in comments, the issue is caused by the navigation code called in the 'service' class. When you call the service method multiple times, it actually changes the current navigation stack in xamarin forms. Move the page navigation code from service class to viewmodel class.
Or try to put the page navigation source code into something like 'NavigationService' (one example is the one in https://learn.microsoft.com/en-us/xamarin/xamarin-forms/enterprise-application-patterns/ ) and inject this service into your view model class.
OK so this all turned out to be an issue with concurrency.
The original button click was like this:
private void Button_Clicked(object sender, EventArgs e)
{
Task.Run(async () => await (BindingContext as ContentPageViewModel).ExecuteNavCommand(sender));
}
But this resulted in a UI operation happening on a different task
The event handler can be declared as async
The correction is
private async void Button_Clicked(object sender, EventArgs e)
{
await viewModel.ExecuteNavCommand(sender);
}

How to handle up button in toolbar - Kotlin

I have manually/programmatically set up an up button in my toolbar for a fragment page with the following code in onCreateOptionsMenu in the fragment:
(activity as AppCompatActivity).setSupportActionBar?.setDisplayHomeAsUpEnabled(true)
Tapping the system back button will take the user back to the previous fragment but without an up button (that works) I think some users may get lost.
I am having difficulty trying to work out how to catch and handle the up button to pop the fragment off the back stack.
This SO post shows how to catch the the up button click however it is in Java and doesn't go on to explain how you would navigate up.
I think it needs to look something like the code below but there are errors everywhere:
The case android.R.id.home is showing an 'Incompatible types:Int and MenuItem' error?
onBackPressed() is showing an 'Unresolved reference' error.
Bad code:
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
when (item) {
android.R.id.home -> {
onBackPressed()
return true
}
else -> return super.onOptionsItemSelected(item)
}
}
UPDATE:
A comment to another SO post has a potential solution in Kotlin. It catches the click on the up button and goes back to the previous fragment page but then the up button doesn't go away. So the up button now persists even on the top level fragment destinations in my app (the pages corresponding to each tab in the BottomNavigationView).
I think this might have to do with the fact that there is only one activity in my app and the way that I have set up the up button in the fragment as mentioned above? If so, is there a workaround or other way to set up the up button by referencing the fragment instead of the whole activity?
If it helps, this is the code in the RecyclerView inner ViewHolder class in the adapter.kt file that navigates to the fragment page in question:
class AdapterListItemDetails(val items: List<ItemsList>) : RecyclerView.Adapter<AdapterListItemDeatils.ItemsViewHolder>() {
//overrides for OnCreateViewHolder, getItemCount, onBindViewHolder
inner class ItemsViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var currentItem: ItemsList? = null
var currentPosition: Int = 0
init {
itemView.setOnClickListener(Navigation.createNavigateOnClickListener(R.id.goto_details, null))
}
fun setData(itemsList: ItemsList, position: Int) {
itemView.tview_Keys.text = itemsList!!.nameText
this.currentItem = itemsList
this.currentPosition = position
}
}
}
You have to override onBackPressed() method in activity and handle the fragment transactions with your manual code. If you could share some snippet of activity and fragment transactions will help me to give some proper solution.
Hi this is what i usually do:
in an activity find the navController from your navHostFragment
val navController = this.findNavController(R.id.myNavHostFragment)
Make sure it's connected to the ActionBar
NavigationUI.setupActionBarWithNavController(this, navController)
Then simply override onSupportNavigateUp, find your navController then navigate up
override fun onSupportNavigateUp(): Boolean{
val navController = this.findNavController(R.id.myNavHostFragment)
return navController.navigateUp()
}

Button Action to Controller and Back Swift

I am new to programming Swift and I have a Button with a Target to a function:
func buttonAction(sender:UIButton!)
{
self.window.rootViewController.presentViewController(FirstViewController(), animated: true, completion: nil);
}
Yet, I have a Button on the FirstView (of FirstViewController) and yet I want to go back to the MainView (of MainViewController) and I get the error Code:
2014-07-30 00:53:44.545 FifthTry[30275:833440] Warning: Attempt to present
<_TtC8FifthTry19FirstViewController: 0x787b5470> on <_TtC8FifthTry18MainViewController:
0x799802d0> whose view is not in the window hierarchy!
What is wrong?
It is great that you split your code into a separate View and Controller class. This is great class design. However, your controller is the one that should handle touch events according to Model-View-Controller which is the paradigm that Apple's frameworks use. The view is for displaying data and the controller is for handling user interaction on that view. If you setup the action on your controller, presenting a view controller is extremely easy because that is the way that Apple encourages you to break down your classes:
func buttonAction(sender:UIButton!)
{
self.presentViewController(FirstViewController(), animated: true, completion: nil)
}

How to pass an NSString from modal view to parent view

I have a parent view and a modal view with a text box. What I am trying to do is pass whatever is entered into the text box from the modal view and then pass it to a label in the parent view which updates the label to what was entered. I hope that made any sense.
I have been pulling my hair out for a couple of weeks trying to figure this out with no luck. I found many examples and tutorials about segues and passing between views that are being pushed but nothing about modal views and passing back to the parent view.
I have been trying to understand this and need a good example. I kind of understand the prepare for segue concept but for some reason, I just can't figure this one out. Any help on this would be much appreciated and you would be my hero for life lol.
In my project that uses segues, here's how I did it (note that I'm new to iOS, so there's probably "better" ways, and this may be obvious to the iOS veterans):
The short version: define a callback protocol in your modal view controller's .h file. When your modal view controller closes, it checks to see if the presenter implements that protocol and invokes those methods to pass along the data.
So like you said, let's say your modal view controller just gathers a single string value from the user and then they click OK or Cancel. That class might look like this:
#interface MyModalViewController : UIViewController
...
#end
I'm suggesting you add a protocol like this to the same header:
#protocol MyModalViewControllerCallback
-(void) userCancelledMyModalViewController:(MyModalViewController*)vc;
-(void) userAcceptedMyModalViewController:(MyModalViewController*)vc
withInput:(NSString*)s;
#end
Then in MyModalViewController.m, you add a viewDidDisappear with code similar to this:
-(void) viewDidDisappear:(BOOL)animated {
UIViewController* presenter = self.presentingViewController;
// If the presenter is a UINavigationController then we assume that we're
// notifying whichever UIViewController is on the top of the stack.
if ([presenter isKindOfClass:[UINavigationController class]]) {
presenter = [(UINavigationController*)presenter topViewController];
}
if ([presenter conformsToProtocol:#protocol(MyModalViewControllerCallback)]) {
// Assumes the presence of an "accepted" ivar that knows whether they
// accepted or cancelled, and a "data" ivar that has the data that the
// user entered.
if (accepted) {
[presenter userAcceptedMyModalViewController:self withInput:data];
}
else {
[presenter userCancelledMyModalViewController:self];
}
}
[super viewDidDisappear:animated];
}
And finally in the parent view, you implement the new #protocol, e.g. in the .h:
#interface MyParentViewController : UIViewController <MyModalViewControllerCallback>
...
#end
and in the .m:
#implementation MyParentViewController
...
-(void) userCancelledMyModalViewController:(MyModalViewController*)vc {
// Update the text field with something like "They clicked cancel!"
}
-(void) userAcceptedMyModalViewController:(MyModalViewController*)vc
withInput:(NSString*)s {
// Update the text field with the value in s
}
...
#end

Resources