I've searched on SO & elsewhere for MvvmCross & Modal, but the one existing answer isn't helping us.
We're developing a cross-platform app using MonoTouch & MvvmCross, which seems to be pretty powerful combination. However, we're having a few issues with the navigation, which we're gradually cracking! The current problem is -
The app runs with a TabBarController, and each tab has navigation to further levels - this works fine. The client however wants the "Start" button on one of the tabs to bring up a modal view (which hides everything else, especially the tab bar), which then has its own levels working in the same manner as a UINavigationController, with the ability to pop back to the tabBarController at any time.
We've managed to bring up a single modal view, but we're stuck on loading new views from here and popping back out.
Any help/advice appreciated!
I think what you're looking to do is to customise the presenter so that it wraps your UIViewController within a UINavigationController - and then modally presents that UINavigationController?
To achieve this, the code in the recent Pull request from #DeapSquatter might help -https://github.com/slodge/MvvmCross/pull/9 - I think you can use his modal nav presenter in order to achieve the effect you are looking for:
if (view is IMvxModalTouchView)
{
if (_currentModalViewController != null)
throw new MvxException("Only one modal view controller at a time supported");
var newNav = new UINavigationController();
newNav.PushViewController(view as UIViewController, false);
_currentModalViewController = view as UIViewController;
PresentModalViewController(newNav, true);
return;
}
The architecture of mvvmcross is deliberately extensible and configurable here - while we include a few basic Presenter classes, it's very likely that people are going to want to customise how different views get presented on an app-by-app basis. Beyond the simplest of demo apps, I anticipate that most mvvmcross apps on touch will ship with a custom presenter inside.
Hope that helps
Stuart
Related
I'm new to Navigator 2.0 in Flutter, and now I'm trying to achieve BottomSheet behavior using declarative approach. Unfortunately, I only managed to come up with quite a cumbersome way to do that so I'm wondering if there's an easier way.
I'm building a shopping app and I want to show the item details card that slides out from the bottom of the screen, dimming the previous page (as shown in picture), when user taps on item in a list. I also want to allow url support, so that /item/id opens home page and then shows the details card over it.
So how I think this could be done is to push (or to add to Navigator's pages array in case of Navigator 2.0) custom non-opaque page with no transition animation using TransitionDelegate and PageRouteBuilder, and then animate dimming and sliding by hand using animation controllers. But this approach seems unnatural (and genuinely scares me to be honest) and it's still not clear to me how to handle back button in this case (Do I need to somehow notify the page to play closing animation from Navigator's onPopPage, is this the way to do this?).
If anyone has an experience of implementing similar feature, I would really appreciate sharing it! Or maybe you have come up with more elegant solution, then tell me about it and I'll give it a try! Thanks!
I have added a Global Button with the following code.
public override void Initialize()
{
if (!String.IsNullOrEmpty(Base.PrimaryView))
{
Type primaryViewItemType = Base.Views[Base.PrimaryView].Cache.GetItemType();
PXAction action = PXNamedAction.AddAction(Base, primaryViewItemType, "SubmitTicket", "Submit Ticket", TestClick);
}
}
public IEnumerable TestClick(PXAdapter adapter)
{
throw new PXException("Button clicked from graph" + Base.GetType().Name);
}
And it renders the button like this in each of the pages.
Now, I would like to display a popup panel, on button's click. I know I can create a popup panel on screen section. But, is there some way that I can have a general popup panel created in one place and can be displayed on each of the pages on the button's click?
Thank you.
As #HB_ACUMATICA mentioned there is no good easy way.
Providing another alternative to his post, you can create a graph and use it as a reusable popup by calling:
throw new PXPopupRedirectException(graph, string.Empty, true)
One thing I ran into was a sizing issue on the popup...
Changing the height/width when calling another graph as an in-page popup using PXPopupRedirectException
If you do copy and paste the PXSmartPanel you can create re-usable business logic by implementing the reusable business logic pattern found in this help as a starting point:
Reusing Business Logic
If I understand correctly you want to share the same PXSmartPanel control in different pages without having to copy/paste it in every screen.
In Acumatica Framework this is achieve by custom container controls like 'PXUploadDialog' which derives functionality from other controls like 'PXSmartPanel'. This is the control that is used when you attach files in all screen.
Unfortunately there seems to be no documentation on how to achieve this.
The closest I found is this SO question which is essentially unanswered:
Create custom User Control for Acumatica
Considering this, you may want to copy/paste the same smart panel in all screen.
To ease copying you can use the 'Edit ASPX' feature, make sure you backup the project before.
Edit ASPX to get to the code:
Copy paste your smart panel in the page and click 'GENERATE CUSTOMIZATION SCRIPT' to package the changes in the project:
In the simple Dialog below:
// choice of layout has no impact:
Container cont=new Container(new TextModeLayout(3, 1));
//Container cont=new Container(new BoxLayout(BoxLayout.Y_AXIS));
TextComponent firstName=new TextComponent().label("First Name").text(person.firstname);
TextComponent lastName=new TextComponent().label("Last Name").text(person.lastname);
TextComponent cost=new TextComponent().label("Cost per Session").text(person.getCostString());
cost.getField().setConstraint(TextArea.DECIMAL);
// NOTE HERE
// doesn't work: // works:
cont.add(firstName); // cont.add(firstName.getField());
cont.add(lastName); // cont.add(lastName.getField());
cont.add(cost); // cont.add(cost.getField());
Dialog.show("Edit Client", cont, new Command(CANCEL), new Command(OK));
Nothing appears in the Dialog unless I add the TextField instead of the TextComponent to my container at the NOTE HERE comment. This means I lose the nice appearance of the labelled input fields (yes I know I could label them myself, but they wouldn't look as good on different devices). My choice of layout manager at the top does not affect this issue (I've tried several). I can't find evidence online to conclude there's an incompatibility here, adding TextComponents and other InputComponents works fine on a Form, just not in a Dialog.
I'm having the same problem in another Dialog that uses PickerComponents. The PickerComponent doesn't appear unless I add the Picker itself, and then the Picker spawned from a Dialog looks all wrong. I'm hoping the simpler code question above will answer this quandary as well.
It's worth noting I've made no theme changes and this problem is noted in both the Android and Apple skins as well as on an actual Android phone. Thanks in advance for any help!
You shouldn't do input in a Dialog as it creates a very problematic user experience. If you would like things to look like they are in a dialog you can use styles and layouts to make a Form feel like a Dialog but you shouldn't use a Dialog.
The reason this fails is a bit complicated but here are the high level problems with using a dialog:
Dialogs don't grow implicitly - This is a huge problem for text input as the component needs space to resize with input and even more so for the animated TextComponent which needs to shift things around. The size of a Dialog is determined when it's shown and that's a big problem
This becomes a bigger problem on Android where the screen resizes during input and distorts the dialog completely. It's one of those things you'll only see on the device because it's really hard to simulate the virtual keyboard.
Scrollability is hard in a Dialog and text components need a scrollable parent so you can scroll between the various edit components
Picker component uses a form of Dialog to show input and this can collide with your dialog
Dialogs are hard to get right for suspend/resume behavior. Suspend/resume happens when the app is minimized or sent to the background. E.g. say you have an incoming call while typing in the dialog. When you go back to the app we want to show the last form. If we show the dialog it will block and we won't know which parent form to show anyway. So when an app is suspended dialogs are just disposed in the default code generated in the main class. It makes more sense.
We try to build an application with a few tabs. As reference-project we use that example: http://slodge.blogspot.co.uk/2013/06/n25-tabs-n1-days-of-mvvmcross.html
To get the ViewModel-instances we need to create the tabs, we used the "HomeViewModel"-pattern as mentioned in that post: Create View Model using MVVMCross built in factory?
What I don't like at this approach is the initialisation of ViewModel's with "new". As far as I understand, it skips the whole ViewModel-Lifecycle (https://github.com/slodge/MvvmCross/wiki/View-Model-Lifecycle) which we really like. In our current project, we'd like to use the "start()" lifecycle-method, but it's never called due to initialisation with "new".
What worked for us was to go that way:
var loaderService = Mvx.Resolve<IMvxViewModelLoader>();
var vm = (UserListViewModel)loaderService.LoadViewModel(new MvxViewModelRequest(typeof(UserListViewModel), null, null, null), null);
So my question: Is that the way to do the job or is it just a dirty workaround and there is a much better solution?
Update: We came to that solution:
CreateTabFor<SettingsViewModel>("Settings", "settings");
//This method loads the ViewModel
private UIViewController CreateTabFor<TTargetViewModel>(string title, string imageName)
where TTargetViewModel : class, IMvxViewModel
{
var controller = new UINavigationController();
controller.NavigationBar.TintColor = UIColor.Black;
var viewModelRequest = new MvxViewModelRequest(typeof(TTargetViewModel), null, null, null);
var screen = this.CreateViewControllerFor<TTargetViewModel>(viewModelRequest) as UIViewController;
SetTitleAndTabBarItem(screen, title, imageName);
controller.PushViewController(screen, false);
return controller;
}
The 'viewmodel lifecycle' is an area of conflicting interests in MvvmCross. The root cause is the conflict between:
viewmodel's which are just the models for any view
viewmodel's which are specifically used within the 'ShowViewModel' navigation process
For simple 'whole page' User Experiences, the C-I-R-S viewmodel lifecycle is easy to support and to ensure it gets consistently used.
However, as soon as the user experience starts to merge in tabs, flyouts, hamburger menus, dialogs, split views, etc then:
the developers sometimes want to control viewmodel lifecycles themselves
it's not as easy for the framework to ensure that view models are always created, activated and tombstoned/rehydrated consistently
Personally, I like your approach - of trying to ensure all viewmodels are independent and all constructed the same way - but MvvmCross doesn't force this approach on all developers.
Specifically for tabs, most of the existing examples do use the 'owned sub-viewmodel' pattern that you've identified.
However, it should be relatively easy to implement other mechanisms if you want to - just as you already have.
In particular, you can:
use the loaderService directly - getting hold of it via Mvx.Resolve<IMvxViewModelLoader>();
use ShowViewModel with a custom presenter to create both views and viewmodels - the beginnings of this is illustrated in that N=25 video but you could take it much further and actually add the tabs in response to ShowViewModel calls.
use alternative calls to create the child tabs and their viewmodels inside the Views - e.g. where the Touch sample currently calls
var screen = this.CreateViewControllerFor(viewModel) as UIViewController;
this could easily be replace with something like:
var screen = this.CreateViewControllerFor<ChildViewModel>() as UIViewController;;
(or one of the other overloads from MvxCanCreateIosViewExtensionMethods.cs)
One repo where I know some users have taken some of these ideas and played with them is the Sliding menu repo - I think they have chosen to use this.CreateViewControllerFor<TViewModel> to create their view models. This may or may not be the way you choose to go - but it might be of interest for you to experiment with.
Forgive me if this has been asked/answered already but I couldn't find it anywhere (at least no in Monotouch - vaguely answer for ObjC. So completely new to Monotouch but I have everything completed in my application that I want with one exception. I'm trying to update a parent of a subview.
Here is the scenario:
I have a UINavigationController with a UIView (not a table) which has a few buttons on it that directs via a PushViewController to a Subview. I make some changes on the subview, which I would like reflected back on the parent. I can of course add a manual refresh button if I wanted but was looking for something a little better.
I did see some things that referred to using a viewWillAppear but couldn't find any good examples. My attempts failed pretty bad, so any suggestions would be great.
Thanks,
Richard
If you have a UINavigationController, it has a "back" button on top. Once this is hit by the user (or if you pop to a previous view controller), the currently view controller shown will trigger ViewWillDisappear and ViewDidDisappear and the view below (the one with your buttons) will fire ViewWillAppear and ViewDidAppear. Just overload those and update your UI state in there.