I'm trying out MvvmCross with Xamarin 'classic'.
I've got it working with Android.
But I can't get it work for iOS. I've taken a look at the sample mentioned here (eh): MVVMCross support for Xamarin.iOS Storyboards
I'm really missing something.
What do i have:
A storyboard with only 3 controls on it. a label and 2 buttons. All 3
have names so i get the properties in the RootViewController class.
The basis setup.cs
AppDelegate.cs
[Register("AppDelegate")]
public partial class AppDelegate : MvxApplicationDelegate
{
UIWindow _window;
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
_window = new UIWindow(UIScreen.MainScreen.Bounds);
StoryBoardTouchViewPresenter sbPresenter = new StoryBoardTouchViewPresenter(this, _window, "MainStoryboard");
var setup = new Setup(this, _window);
setup.Initialize();
var startup = Mvx.Resolve<IMvxAppStart>();
startup.Start();
sbPresenter.MasterNavigationController.NavigationBar.Translucent = false;
sbPresenter.MasterNavigationController.SetNavigationBarHidden(false, false);
return true;
}
}
StoryBoardTouchViewPresenter (from MVVMCross: Is it possible to use Storyboard with ICommand navigation?) But the API is changed.
public class StoryBoardTouchViewPresenter : MvxTouchViewPresenter
{
public static UIStoryboard Storyboard = null;
public StoryBoardTouchViewPresenter(UIApplicationDelegate applicationDelegate, UIWindow window, string storyboardName, NSBundle StoryboardBundleOrNull = null)
: base(applicationDelegate, window)
{
Storyboard = UIStoryboard.FromName(storyboardName, StoryboardBundleOrNull);
}
public override void Show(IMvxTouchView view)
{
MvxViewController sbView = null;
try
{
sbView = (MvxViewController)Storyboard.InstantiateViewController(view.Request.ViewModelType.Name.Replace("Model", ""));
}
catch (Exception e)
{
Console.WriteLine("Failed to find storyboard view, did you forget to set the Storyboard ID to the ViewModel class name without the Model suffix ?" + e);
}
sbView.Request = view.Request;
base.Show(sbView);
}
}
The default App.cs in the Core project
public class App : Cirrious.MvvmCross.ViewModels.MvxApplication
{
public override void Initialize()
{
CreatableTypes()
.EndingWith("Service")
.AsInterfaces()
.RegisterAsLazySingleton();
RegisterAppStart<ViewModels.MainViewModel>();
}
}
The ViewModel:
public class MainViewModel : MvxViewModel
{
ITodoTaskService taskService;
IDataManager<TodoTask> tasks;
public MainViewModel(ITodoTaskService taskService)
{
this.taskService = taskService;
}
public async override void Start()
{
this.tasks = new DataManager<TodoTask>(await this.taskService.GetTodoTasksAsync());
this.tasks.MoveFirst();
Rebind();
base.Start();
}
private void Rebind()
{
this.Description = this.tasks.Current.Description;
NextCommand.RaiseCanExecuteChanged();
PreviousCommand.RaiseCanExecuteChanged();
}
private string description;
public string Description
{
get { return this.description; }
set
{
this.description = value;
RaisePropertyChanged(() => Description);
}
}
private MvxCommand nextCommand;
public MvxCommand NextCommand
{
get
{
this.nextCommand = this.nextCommand ?? new MvxCommand(NavigateToNext, CanNavigateNext);
return this.nextCommand;
}
}
private bool CanNavigateNext()
{
return this.tasks.CanMoveNext;
}
public void NavigateToNext()
{
this.tasks.MoveNext();
Rebind();
}
private MvxCommand previousCommand;
public MvxCommand PreviousCommand
{
get
{
this.previousCommand = this.previousCommand ?? new MvxCommand(NavigateToPrevious, CanNavigatePrevious);
return this.previousCommand;
}
}
private bool CanNavigatePrevious()
{
return this.tasks.CanMovePrevious;
}
public void NavigateToPrevious()
{
this.tasks.MovePrevious();
Rebind();
}
}
I tried all kind of things. At the moment i get an exception that the MainView cannot be found. Which i partly understand. in App.cs MainViewModel is the start up. But the controller is called RootViewController. I think the RootviewController should bind to my MainViewModel. But i don't know how.
How should I make MvvmCross with iOs working?
How should I name the parts?
MvvmCross' default view finder will look for a view called MainView. That view should be derived from MvxViewController or another IMvxTouchView type. If you don't want to name your view controller "MainView" then you need to create a custom view resolver.
My advice: just rename your RootViewController to MainView.
Related
I am using QuickLook to preview Images, Pdf and Microsoft office documents. It is working fine to preview documents but its ShouldOpenUrl delegate method not firing whenever i try to open link from documents. Following is the code that i tried.
I test my app with iPhone and iPad having iOS v11.
// Open documents using title and file url
public void OpenDocument(string title, string url)
{
var rootViewController = UIApplication.SharedApplication.KeyWindow.RootViewController;
var previewViewController = new QLPreviewController();
previewViewController.DataSource = new DocumentPreviewDataSource(title, url);
previewViewController.Delegate = new PreviewControllerDelegate();
rootViewController.PresentViewController(previewViewController, true, null);
}
// QLPreviewControllerDelegate Implementation
public class PreviewControllerDelegate : QLPreviewControllerDelegate
{
public override bool ShouldOpenUrl(QLPreviewController controller, NSUrl url, IQLPreviewItem item)
{
Console.WriteLine("PreviewControllerDelegate::ShouldOpenUrl: {0}", url.AbsoluteString);
return true;
}
}
You can use the weakdelegate
public partial class xxxViewController : UIViewController,IQLPreviewControllerDelegate,IQLPreviewControllerDataSource
//. . .
in method OpenDocument
public void OpenDocument()
{
var previewViewController = new QLPreviewController();
previewViewController.View.Frame = View.Bounds;
previewViewController.WeakDelegate = this;
previewViewController.WeakDataSource = this;
this.PresentViewController(previewViewController, true,null);
}
And override the method in QLPreviewControllerDelegate and QLPreviewControllerDataSource
public nint PreviewItemCount(QLPreviewController controller)
{
return 1;
}
public IQLPreviewItem GetPreviewItem(QLPreviewController controller, nint index)
{
return new NSUrl("your url");
}
[Export("previewController:shouldOpenURL:forPreviewItem:")]
public bool ShouldOpenUrl(QLPreviewController controller, NSUrl url, IQLPreviewItem item)
{
Console.WriteLine("PreviewControllerDelegate::ShouldOpenUrl: {0}", url.AbsoluteString);
return true;
}
[Export("previewControllerWillDismiss:")]
public void WillDismiss(QLPreviewController controller)
{
// do some thing
}
I use the above code and it works fine.
I am using MvvmCross in my Xamarin.Android application. I want to make my own custom MvxRecyclerAdapter so that I can have multiple buttons in each row of the MvxRecyclerView. Here is my custom MvxRecyclerView:
public class TwoPieceMvxRecyclerView : MvxRecyclerView
{
private bool _initialized;
public TwoPieceMvxRecyclerView(Context context, IAttributeSet attr) : base(context, attr)
{
}
public override Android.Support.V7.Widget.RecyclerView.Adapter GetAdapter()
{
if(!_initialized)
{
SetAdapter(new TwoPieceMvxRecyclerAdapter());
_initialized = true;
}
return base.GetAdapter();
}
}
And here is my custom MvxRecyclerAdapter:
public class TwoPieceMvxRecyclerAdapter : MvxRecyclerAdapter, IOnClickListener
{
private ICommand _itemClickPiece1;
private ICommand _itemClickPiece2;
private View _clickablePiece1;
private View _clickablePiece2;
public TwoPieceMvxRecyclerAdapter()
{
}
public ICommand ItemClickPiece1
{
get { return _itemClickPiece1; }
set
{
if (ReferenceEquals(_itemClickPiece1, value))
{
return;
}
_itemClickPiece1 = value;
}
}
public ICommand ItemClickPiece2
{
get { return _itemClickPiece2; }
set
{
if (ReferenceEquals(_itemClickPiece2, value))
{
return;
}
_itemClickPiece2 = value;
}
}
protected override Android.Views.View InflateViewForHolder(Android.Views.ViewGroup parent, int viewType, MvvmCross.Binding.Droid.BindingContext.IMvxAndroidBindingContext bindingContext)
{
var view = base.InflateViewForHolder(parent, viewType, bindingContext);
_clickablePiece1 = view.FindViewById<View>(Resource.Id.clickable_piece1);
_clickablePiece2 = view.FindViewById<View>(Resource.Id.clickable_piece2);
_clickablePiece1.SetOnClickListener(this);
_clickablePiece2.SetOnClickListener(this);
return view;
}
public void OnClick(View v)
{
if (v == _clickablePiece1)
{
ItemClickPiece1.Execute(null);
}
else if (v == _clickablePiece2)
{
ItemClickPiece2.Execute(null);
}
}
}
When I run the application I get this error:
Could not activate JNI Handle 0xbfd00978 (key_handle 0x6e44919) of
Java type
'md5bd77c484e80df14e69d8c5ab04394fe0/TwoPieceMvxRecyclerView' as
managed type
'AzzimovMobile.Droid.Components.TwoPieceMvxRecycler.TwoPieceMvxRecyclerView'.
System.InvalidOperationException: If you wan't to use single
item-template RecyclerView Adapter you can't change
it'sIMvxTemplateSelector to anything other than
MvxDefaultTemplateSelector
You are missing a constructor on your RecyclerView:
public TwoPieceMvxRecyclerView(IntPtr javaReference, JniHandleOwnership transfer): base(javaReference, transfer)
{
}
Also be aware you don't need to use a custom RecyclerView to change its Adapter. You can just grab the RecyclerView instance on your .cs view and set the adapter from there. Something like this should work:
public class MyView: MvxFragment<MyViewModel>
{
//...
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
var view = base.OnCreateView(inflater, container, savedInstanceState);
// ...
var recycler = view.FindViewById<MvxRecyclerView>(Resource.Id.recycler);
recycler.Adapter = new TwoPieceMvxRecyclerAdapter(((IMvxAndroidBindingContext)BindingContext);
// you can even set a TemplateSelector here!
recycler.ItemTemplateSelector = new MyTemplateSelector();
// ...
return view;
}
}
In Xamarin for iOS development, is there a way I could refresh a view from its ViewModel?
I am using MVVMCross, if it helps.
Please advice.
Thanks.
What I do in my projects is to use MvvmCross' Messenger plugin to broadcast a message. Then in the View, subscribe for that message, and when one is broadcasted, refresh the view accordingly.
First, create a Message class extending MvxMessage.
public class RefreshViewMessage : MvxMessage
{
// Add other properties if needed
// public string SomeParameter { get; set; }
public RefreshViewMessage(object sender) : base(sender)
{
}
}
Second, broadcast that message in the ViewModel.
public class ViewModel : MvxViewModel
{
private IMvxMessenger _messenger;
public MainViewModel(IMvxMessenger messenger)
{
_messenger = messenger;
}
public void RefreshView()
{
_messenger.Publish(new RefreshViewMessage(this));
// Maybe some parameters need to be attached
// var message = new RefreshViewMessage(this) { SomeParameter = "stuff" };
// _messenger.Publish(message);
}
}
Third, subscribe for that message in the View.
public partial class View : MvxViewController<ViewModel>
{
public View(IntPtr handle) : base(handle) { }
public View() : base() { }
private IMvxMessenger _messenger;
private MvxSubscriptionToken _token; // keep a subscription token to prevent untimely garbage collection
public override void ViewDidLoad()
{
base.ViewDidLoad();
_messenger = Mvx.Resolve<IMvxMessenger>();
_token = _messenger.SubscribeOnMainThread<RefreshViewMessage>(OnRefreshView);
}
private void OnRefreshView(RefreshViewMessage message)
{
// Access data attached to the message if needed
// var param = message.SomeParameter;
// Refresh view
}
}
I've got problem with data-binding in mvvmcross after doing the navigation in the model by calling the showviewmodel-method. On the android side it works.
So the Problem is, that the navigation itself is working but I don't get any data from the model.
Navigation in the model:
ShowViewModel<TeamEventDetailsViewModel>(new { eventID = item.ID });
ViewModel which containts the Data:
public class TeamEventDetailsViewModel
: EventDetailsViewModel
{
public TeamEventModel CurrentEvent
{
get { return MyCurrentEvent as TeamEventModel; }
set
{
MyCurrentEvent = value;
RaisePropertyChanged(() => CurrentEvent);
TickerModel.Comments = value.Comments;
RaisePropertyChanged(() => TickerModel);
LineupModel.Team1Players = value.Team1Players;
LineupModel.Team2Players = value.Team2Players;
RaisePropertyChanged(() => LineupModel);
}
}
private EventDetailsLineupViewModel _lineupModel = new EventDetailsLineupViewModel();
public EventDetailsLineupViewModel LineupModel
{
get { return _lineupModel; }
set { _lineupModel = value; RaisePropertyChanged(() => LineupModel); }
}
public TeamEventDetailsViewModel()
{
EventToken = MvxMessenger.Subscribe<EventUpdateMessage>(OnEventUpdateMessage);
}
private void OnEventUpdateMessage(EventUpdateMessage eventUpdate)
{
if (MyCurrentEvent != null && eventUpdate.Event.ID == MyCurrentEvent.ID)
{
var updatedEvent = (TeamEventModel)eventUpdate.Event;
var myEvent = CurrentEvent;
if(updatedEvent.Score!=null)
myEvent.Score = updatedEvent.Score;
if (updatedEvent.Team1Players != null)
myEvent.Team1Players = updatedEvent.Team1Players;
if (updatedEvent.Team2Players != null)
myEvent.Team2Players = updatedEvent.Team2Players;
CurrentEvent = myEvent;
}
}
protected override void Update(EventModel eventdetails)
{
CurrentEvent = (TeamEventModel) eventdetails;
}
private string _teststring = "success";
public string Teststring
{
get { return _teststring; }
set
{
_teststring = value;
RaisePropertyChanged(()=>_teststring);
}
}
}
As you can see at the bottom I implemented a teststring to prove functionality.
Binding in the View:
public class TeamEventDetailsView : MvxViewController
{
public UILabel TestLabel = new UILabel();
public TeamEventDetailsViewModel TeamEventDetailsViewModel
{
get { return (TeamEventDetailsViewModel)base.ViewModel; }
set { base.ViewModel = value; }
}
public override void ViewDidLoad()
{
View.AddSubview(TestLabel);
this.CreateBinding(TestLabel).To<TeamEventDetailsViewModel>(vm => vm.Teststring).Apply();
TestLabel.BackgroundColor = UIColor.Orange;
}
public override void ViewDidLayoutSubviews()
{
base.ViewDidLayoutSubviews();
TestLabel.Frame=new RectangleF(0,20,View.Frame.Width,80);
}
}
So I repeat, the navigation itself works but the data from model doesn't get shown on the view.
If I create the ViewModel manually in the View then the binding works also, but in my Situation I can't do that because the Data is pulled depending on the generated Data from the ViewModel which calls the navigation-proceed.
Manual ViewModel:
TeamEventDetailsViewModel = new TeamEventDetailsViewModel();
TeamEventDetailsViewModel.Init(9816);
As I can tell I did exactly the same as Stuard does in his Tutorial:
https://www.youtube.com/watch?v=cbdPDZmuHk8
Does anyone has an advice for me?
Thanks.
MvvmCross does the ViewModel create in base.ViewDidLoad() - if you add that call to your ViewDidLoad override then everything should work ok
How can I bind Command to recieve taps on sections of my Table? I am using MvxViewController with custom TableSource, but it seems that I cant add bindings to my VM when creating UIView for sections.
Here is my ViewModel:
public class TestViewModel : MvxViewModel
{
private ObservableCollection<string> _sections;
public ObservableCollection<string> Sections
{
get { return _sections; }
set { _sections = value; RaisePropertyChanged(() => Sections); }
}
private MvxCommand _sectionTappedCommand;
public ICommand SectionTappedCommand
{
get
{
_sectionTappedCommand = _sectionTappedCommand ?? new MvxCommand(DoSectionTappedCommand);
return _sectionTappedCommand;
}
}
private void DoSectionTappedCommand()
{
//I want this command somehow to be called when user taps section header
Debug.WriteLine("Section tapped!");
}
}
My view:
[Register("TestView")]
public class OrderView : MvxViewController
{
public override void ViewDidLoad()
{
View = new UIView() { BackgroundColor = UIColor.White };
base.ViewDidLoad();
var table = new UITableView(new RectangleF(0, 20, 320, 660));
Add(table);
var source = new TestTableSource(table);
table.Source = source;
var set = this.CreateBindingSet<OrderView, OrderViewModel>();
// I think here I need to write something like:
// set.Bind(source).For(s => s.Section.TapAction).To(vm => vm.SectionTappedCommand);**
set.Bind(source).For(s => s.ItemsSource).To(vm => vm.Sections).OneWay();
set.Apply();
}
}
Table source:
public class TestTableSource : MvxBaseTableViewSource
{
// all needed overrides implemented
private IList<OrderGuest> _sections;
public IList<OrderGuest> ItemsSource
{
get
{
return _sections;
}
set
{
_sections = value;
ReloadTableData();
}
}
public override UIView GetViewForHeader(UITableView tableView, int section)
{
// Do I need to add bindings here?
var view = new OrderGuestSectionHeader(OrderGuestSectionHeader(tableView, section), () => {
Debug.WriteLine("selected " + section.ToString());
});
return view;
}
public override int NumberOfSections(UITableView tableView)
{
if (_sections == null)
return 0;
return _sections.Count;
}
public override string[] SectionIndexTitles(UITableView tableView)
{
if (_sections == null)
return null;
return _sections.Select(x => x.Name).ToArray();
}
}
Subclassed UIView for section header:
public sealed class OrderGuestSectionHeader : UIView
{
private UIButton SectionButton;
public Action TapAction;
public OrderGuestSectionHeader(string header, Action tapped)
{
Frame = new RectangleF(0, 0, 320, 20);
BackgroundColor = UIColor.Blue;
SectionButton = new UIButton(this.Frame);
SectionButton.TouchUpInside += SectionButton_TouchUpInside;
SectionButton.Title(header);
TapAction = tapped;
Add(SectionButton);
}
private void SectionButton_TouchUpInside(object sender, EventArgs e)
{
TapAction();
}
}
There are a few ways you could achieve this effect.
For just your current requirements, the easiest way to achieve it would be to add just a single command to the TestTableSource and to have that command passed on to your section header inside the Action handler.
public class TestTableSource : MvxBaseTableViewSource
{
public ICommand FooCommand { get; set; }
// existing code
public override UIView GetViewForHeader(UITableView tableView, int section)
{
var view = new OrderGuestSectionHeader(OrderGuestSectionHeader(tableView, section), () => {
Debug.WriteLine("selected " + section.ToString());
if (FooCommand != null) FooCommand.Execute(null);
});
return view;
}
}
This Command can then be bound to the ViewModel by adding the OrderView binding:
set.Bind(source)
.For(s => s.FooCommand)
.To(vm => vm.SectionTappedCommand)
.OneWay();
If you wanted to go further - if you wanted to do a more complicated binding - then you could actually set up a full binding DataContext for the Header View. The easiest way to do this would be to inherit from MvxView. I won't go into the full detail of this here - instead for an introduction to MvxView, see the N+1 video - http://slodge.blogspot.co.uk/2013/06/n32-truth-about-viewmodels-starring.html with sample source code in https://github.com/slodge/NPlus1DaysOfMvvmCross/tree/master/N-32-ViewModels