Scrolling to start of Xamarin Forms ListView with header - xamarin

I'm having some trouble scrolling to the top of a ListView in Xamarin Forms. I can scroll to the first item by calling ScrollTo and passing the first item. The problem is that when the list has a header item, I can't find a way to scroll to the header. Is this possible? The only work around I can think of is to not use the header and just have another item at the start of the ItemSource list that acts as a header but I'd rather use the header if possible. Thanks.

So I've solved this myself now. My solution was to subclass ListView and add a public ScrollToTop method which invokes an internal ScrollToTopRequestedEvent when called. I then subclassed the ListViewRenderer on each platform and registered for the event.
In the Android renderer I'm then calling Control.SmoothScrollToPositionFromTop(0, 0) to scroll to top.
In the iOS rendered I'm calling Control.ScrollRectToVisible(new CoreGraphics.CGRect(0, 0, 1, 1), true).

Wah all credits #Gareth Wynn, man that was cool thx.
Anyway here's the code for everyone to use, change class names and namespace, iOS not included, do same as for Android just using Gareth Wynn's hint in parallel answer:
SHARED NiftyListView.cs :
using System;
using Xamarin.Forms;
namespace AppoMobi
{
public class NiftyListView : CListView
{
public event EventHandler EventScrollToTop;
//-------------------------------------------------------------------
public void ScrollToTop(bool animate=true)
//-------------------------------------------------------------------
{
//bool animate is not used at this stage, it's always animated.
EventScrollToTop?.Invoke(this, EventArgs.Empty);
}
}
}
ANDROID NiftyListView.Android.cs :
using System;
using AppoMobi;
using AppoMobi.Droid.Renderers;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using ListView = Xamarin.Forms.ListView;
[assembly: ExportRenderer(typeof(NiftyListView), typeof(NiftyListViewRenderer))]
namespace AppoMobi.Droid.Renderers
{
//-------------------------------------------------------------------
class NiftyListViewRenderer : ListViewRenderer
//-------------------------------------------------------------------
{
protected override void OnElementChanged(ElementChangedEventArgs<ListView> e)
{
base.OnElementChanged(e);
var view = (NiftyListView)Element;
if (view == null) return;
view.EventScrollToTop += OnScrollToTop;
}
//-------------------------------------------------------------------------------------------
public async void OnScrollToTop(object sender, EventArgs eventArgs)
//-------------------------------------------------------------------------------------------
{
Control.SmoothScrollToPositionFromTop(0, 0);
}
}
}

ScrollTo(Object, Object, ScrollToPosition, Boolean)
Scrolls the ListView to the item in the group
https://learn.microsoft.com/en-us/dotnet/api/xamarin.forms.listview.scrollto?view=xamarin-forms
Model for each group:
public class Notification {
public int Id { get; set; }
public string Title { get; set; };
public Notification(int id, string title) {
Id = id;
Title = title;
}
}
Group-Model for ItemSource:
public class NotificationGroup: List<Notification> {
public string Title { get; set; }
public string ShortTitle { get; set; }
public NotificationGroup(string title) {
Title = title;
}
}
Sample Data & Usage:
//SAMPLE DATA
var notifications = new ObservableCollection <NotificationGroup> {
new NotificationGroup("Group-01") {
new Notification(1, "Item-1"),
new Notification(2, "Item-2")
},
new NotificationGroup("Group-02") {
new Notification(3, "Item-3"),
new Notification(4, "Item-4")
}
};
YourListViewName.ItemSource = notifications;
//USING
YourListViewName.ScrollTo(notifications.First()[0], notifications.First(), ScrollToPosition.Start, true);

Related

Getting initial data pattern in Xamarin Forms

I'm trying to understand the pattern to use in Xamarin Forms when a page gets its initial data from a web API.
The page is tied to a ViewModel. Let's use this simple example:
public class DataFeedViewModel : BaseViewModel
{
public DateFeedViewModel()
{
Title = "My Feed";
}
public List<FeedItem> Feed { get; set; }
}
The DataFeedViewModel is bound to the page:
public MainPage()
{
InitializeComponent();
this.BindingContext = new DataFeedViewModel();
}
As I understand it, I use the OnAppearing() method to fetch my initial set of data from the backend API:
protected override async void OnAppearing()
{
base.OnAppearing();
var result = await _myApiService.GetFeed();
// What's next? Do I simply do the following?
// new DataFeedViewModel
// {
// Feed = result
// }
}
Also a second but very important question is whether this pattern is the recommended approach.
As I learn about Xamarin and .NET Maui, I understand, the trend is to go from an event driven model to a more MVVM command driven approach.
I'm a bit confused about how to use a ViewModel to tap into these life cycle methods such as OnAppearing().
create an Init method on your VM
public class DataFeedViewModel : BaseViewModel
{
public DateFeedViewModel()
{
Title = "My Feed";
}
public List<FeedItem> Feed { get; set; }
public async void Init()
{
Feed = await _myApiService.GetFeed();
}
}
and then have your page call it
private DataFeedViewModel VM { get; set; }
public MainPage()
{
InitializeComponent();
this.BindingContext = VM = new DataFeedViewModel();
}
protected override async void OnAppearing()
{
base.OnAppearing();
await VM.Init();
}

Removing back swipe gesture from page using xamarin forms

Is there a way i can disable the back swipe to previous page option for iOS on one single page of my project ?
You can achieve this by implementing a custom renderer and setting the right property for this. You can see a sample implementation underneath. The right property, in this case, is InteractivePopGestureRecognizer which you need to set to false.
Do this in the ViewWillAppear so the NavigationController is initialized.
using DisableSwipe.iOS;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(ContentPage), typeof(NoBackSwipeRenderer))]
namespace DisableSwipe.iOS
{
public class NoBackSwipeRenderer : PageRenderer
{
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
if (ViewController?.NavigationController != null)
ViewController.NavigationController.InteractivePopGestureRecognizer.Enabled = false;
}
}
}
#Symorp
You could do it like so:
public class YourCustomPageRenderer : PageRenderer
{
private YourCustomPage _yourCustomPage;
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
_yourCustomPage = e.NewElement as YourCustomPage;
if (_yourCustomPage != null)
{
_yourCustomPage.PropertyChanged += YourCustomPagePropertyChangedEventHandler;
}
}
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
SetInteractivePopGestureRecognizerEnabled(isEnabled: false);
}
private void YourCustomPagePropertyChangedEventHandler(object sender, PropertyChangedEventArgs propertyChangedEventArgs)
{
if (propertyChangedEventArgs.PropertyName == nameof(YourCustomPage.IsInteractivePopGestureRecognizerEnabled))
{
SetInteractivePopGestureRecognizerEnabled(_yourCustomPage.IsInteractivePopGestureRecognizerEnabled);
}
}
private void SetInteractivePopGestureRecognizerEnabled(bool isEnabled)
{
var interactivePopGestureRecognizer = ViewController?.NavigationController?.InteractivePopGestureRecognizer;
if (interactivePopGestureRecognizer != null)
{
//Prevents the back-swipe-gesture when the user wants to swipe a page away (from left edge of the screen)
interactivePopGestureRecognizer.Enabled = isEnabled;
}
}
}
public class YourCustomPage : ContentPage
{
/// <summary>
/// If you need it as bindable property, feel free to create a <see cref="BindableProperty"/>.
/// </summary>
public bool IsInteractivePopGestureRecognizerEnabled { get; set; }
}
Feel free to adjust to your needs! :-)
I omitted the export renderer attribute etc., just for simplicity.

Custom cell is not updating viewmodel when user enters input into cell

I am new into MVVMCross(Xamarin.iOS). So, I could be in wrong direction. If somebody can point me in right direction or point out what I am doing wrong.
I have already taken a look over "CustomerManagement" and "Collection" sample of MVVMCross.
Basically I am trying to create Settings screen.
I have one custom UITableViewCell with one text field. See my code below. "EntryTF" is IBOutleted. "EntryTF" is binded with "FirstName" property of Model. Setting value to "FirstName" is reflecting on UI. But if user enter something to textfield doesn't save to model. In short cell is not updating viewmodel/model.
Please note that I want to keep binding out of cell class. So, I can reuse this cell for other models or fields.
public partial class PlainEntryCell : UITableViewCell
{
public static readonly UINib Nib = UINib.FromName ("PlainEntryCell", NSBundle.MainBundle);
public static readonly NSString Key = new NSString ("PlainEntryCell");
public PlainEntryCell (IntPtr handle) : base (handle)
{
// this.DelayBind (() => {
// this.AddBindings(new Dictionary<object,string> ())
// });
}
public static PlainEntryCell Create ()
{
return (PlainEntryCell)Nib.Instantiate (null, null) [0];
}
public string CaptionText {
get {
return EntryTF.Text;
}
set {
EntryTF.Text = value;
}
}
}
My View Model:
public class RegisterViewModel: MvxViewModel
{
private RegisterModel _registerModel;
public RegisterViewModel ()
{
_registerModel = new RegisterModel ();
_registerModel.FirstName = "Test";
}
public RegisterModel Customer {
get { return _registerModel; }
set {
_registerModel = value;
RaisePropertyChanged ("Customer");
}
}
}
Model:
public class RegisterModel:MvxNotifyPropertyChanged
{
private string _firstName;
public string FirstName {
get { return _firstName; }
set {
_firstName = value;
RaisePropertyChanged ("FirstName");
}
}
public string LastName { get; set; }
public string PhoneNum { get; set; }
public string Email { get; set; }
public string Pin { get; set; }
}
TableView Source:
public class RegisterTableViewSource: MvxTableViewSource
{
RegisterView _registerView;
public RegisterTableViewSource (UITableView tableView, RegisterView registerView)
: base (tableView)
{
_registerView = registerView;
tableView.RegisterNibForCellReuse (PlainEntryCell.Nib,
PlainEntryCell.Key);
//tableView.RegisterNibForCellReuse (UINib.FromName ("DogCell", NSBundle.MainBundle), DogCellIdentifier);
}
protected override UITableViewCell GetOrCreateCellFor (UITableView tableView, Foundation.NSIndexPath indexPath, object item)
{
var cell = TableView.DequeueReusableCell (PlainEntryCell.Key, indexPath);
cell.Bind (_registerView, "CaptionText Customer.FirstName");
return cell;
//return (UITableViewCell)TableView.DequeueReusableCell (PlainEntryCell.Key, indexPath);
}
public override nint RowsInSection (UITableView tableview, nint section)
{
return 2;
}
}
Update:
Still not able to get the answer.
In above code I want to bind "EntryTF" with property of model. But I want to keep binding outside class. So, CaptionText property is not necessary if someone can point out direct way of binding to "EntryTF"
Is not there a way of creating BindableProperty just like Xamarin Forms? I feel MVVMCross is matured framework so, why there is not a solution of this kind of simple things.
I would also love to here if there is any simple/other way of achieving the same things.
I have also looked MTD but didn't find much useful for custom cell and it is also need good amount of my learning.
Basically I am trying to create Settings screen.
Take a look at using monotouch dialog, mt.d, with MvvmCross, Use Mvvmcross Binding with MonoTouch.Dialog (Lists and Commands)
That StackOverflow question/answer will get you started using mt.d with mvvmcross. I use it for basic settings screens in my applications.
Cell Isn't Updating
I wrote an article on using custom cells with MvvmCross, see
http://benjaminhysell.com/archive/2014/04/mvvmcross-custom-mvxtableviewcell-without-a-nib-file/
I have found it easier not to use nib files and fully describe my UI in code.
It looks like in your public partial class PlainEntryCell : UITableViewCell you don't ever bind the cell to the ViewModel. You have that commented out. I would try something like, while adding http://slodge.blogspot.com/2013/07/playing-with-constraints.html to your application for layout:
public PlainEntryCell()
{
CreateLayout();
InitializeBindings();
}
UILabel captionText;
private void CreateLayout()
{
SelectionStyle = UITableViewCellSelectionStyle.None;
Accessory = UITableViewCellAccessory.DisclosureIndicator;
captionText = new UILabel();
ContentView.AddSubviews(captionText);
ContentView.SubviewsDoNotTranslateAutoresizingMaskIntoConstraints();
const int vPadding = 10;
const int hPadding = 20;
ContentView.AddConstraints(
captionText.AtTopOf(ContentView).Plus(vPadding),
captionText.AtLeftOf(ContentView).Plus(hPadding),
captionText.Width().EqualTo(UIScreen.MainScreen.Bounds.Width / 2)
);
private void InitializeBindings()
{
this.DelayBind(() =>
{
var set = this.CreateBindingSet<PlainEntryCell, RegisterViewModel();
set.Bind(captionText).To(vm => vm.CaptionText);
set.Apply();
});
}
}
Your custom cell needs to implement INotifyPropertyChanged to allow the ViewModel to be notified of a value change in your cell properties.
public partial class PlainEntryCell : UITableViewCell, INotifyPropertyChanged
{
public static readonly UINib Nib = UINib.FromName ("PlainEntryCell", NSBundle.MainBundle);
public static readonly NSString Key = new NSString ("PlainEntryCell");
public PlainEntryCell (IntPtr handle) : base (handle)
{
// this.DelayBind (() => {
// this.AddBindings(new Dictionary<object,string> ())
// });
}
public static PlainEntryCell Create ()
{
return (PlainEntryCell)Nib.Instantiate (null, null) [0];
}
public string CaptionText {
get {
return EntryTF.Text;
}
set {
EntryTF.Text = value;
RaisePropertyChanged("CaptionText");
}
}
#region INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}

How to load PDF in Xamarin Forms

I have a Xamarin Forms app where I want to open a locally stored PDF. I don't need to load them within the app, I'm fine with shelling out to the device's default document viewer for PDFs. How can I do this?
I tried sending a WebView to the PDF, but that didn't work, I just got a blank page.
I've recently done this in my own project using a custom renderer. First implement an empty Xamarin forms view such as (I've included a bindable FilePath attribute):
public class PdfViewer : View
{
public static readonly BindableProperty FilePathProperty =
BindableProperty.Create<DocumentViewer, string>(p => p.FilePath, null);
public string FilePath
{
get
{
return (string)this.GetValue(FilePathProperty);
}
set
{
this.SetValue(FilePathProperty, value);
}
}
}
Then create an iOS Renderer that will be registered for this control. This renderer can, as it is within an iOS project, use the Quick Look Preview Controller to delegate to the built in iOS pdf viewer:
[assembly: ExportRenderer(typeof(PdfViewer), typeof(DocumentViewRenderer))]
public class DocumentViewRenderer
: ViewRenderer<PdfViewer, UIView>
{
private QLPreviewController controller;
protected override void OnElementChanged(ElementChangedEventArgs<DocumentViewer> e)
{
base.OnElementChanged(e);
this.controller = new QLPreviewController();
this.controller.DataSource = new DocumentQLPreviewControllerDataSource(e.NewElement.FilePath);
SetNativeControl(this.controller.View);
}
private class DocumentQLPreviewControllerDataSource : QLPreviewControllerDataSource
{
private string fileName;
public DocumentQLPreviewControllerDataSource(string fileName)
{
this.fileName = fileName;
}
public override int PreviewItemCount(QLPreviewController controller)
{
return 1;
}
public override QLPreviewItem GetPreviewItem(QLPreviewController controller, int index)
{
var documents = NSBundle.MainBundle.BundlePath;
var library = Path.Combine(documents, this.fileName);
NSUrl url = NSUrl.FromFilename(library);
return new QlItem(string.Empty, url);
}
private class QlItem : QLPreviewItem
{
public QlItem(string title, NSUrl uri)
{
this.ItemTitle = title;
this.ItemUrl = uri;
}
public override string ItemTitle { get; private set; }
public override NSUrl ItemUrl { get; private set; }
}
}
}
I haven't compiled and run this as I've extracted it from my larger project but in general this should work.
I had to do something and solve it using a DependencyService . You can use it to open the pdf depending on each platform
I show you an example of how to solve it on Android :
IPdfCreator.cs:
public interface IPdfCreator
{
void ShowPdfFile();
}
MainPage.cs:
private void Button_OnClicked(object sender, EventArgs e)
{
DependencyService.Get<IPdfCreator>().ShowPdfFile();
}
PdfCreatorAndroid.cs
[assembly: Dependency(typeof(PdfCreatorAndroid))]
namespace Example.Droid.DependecyServices
{
public class PdfCreatorAndroid : IPdfCreator
{
public void ShowPdfFile()
{
var fileLocation = "/sdcard/Template.pdf";
var file = new File(fileLocation);
if (!file.Exists())
return;
var intent = DisplayPdf(file);
Forms.Context.StartActivity(intent);
}
public Intent DisplayPdf(File file)
{
var intent = new Intent(Intent.ActionView);
var filepath = Uri.FromFile(file);
intent.SetDataAndType(filepath, "application/pdf");
intent.SetFlags(ActivityFlags.ClearTop);
return intent;
}
}
}
Result:
http://i.stack.imgur.com/vrwzt.png
Here there is a good project that uses MuPDF Library in xamarin . I've tested it and it works properly.
With MuPDF you can zoom out , zoom in and even write some note on PDFs.

Windows Phone Page navigation in MVVM

I am using MVVM in one of the app. I have created different project for Model, View and View Model.
I need to navigate to another XAML from ViewModel. I found some solution using MVVM light. Is there any way of implementing navigation from view model without using MVVM light.
Simple as that,
IF you want to navigate from page1 to page2,
private void MoveToPage2FromPage1()
{
NavigationService.Navigate(new Uri("/Page2.xaml", UriKind.Relative));
}
How to perform page navigation on Windows Phone 8
You can store current page url on a notify property of Shared ViewModel in App. After that, it is easy to catch the change of this url and navigate to the correct url by observing it.
public class AppViewModel : INotifyPropertyChanged
{
public string CurrentPageURL { get; set; }
private string _currentPageURL;
public string CurrentPageURL
{
get { return _currentPageURL;}
set
{
if (_currentPageURL==value)
return; // to prevent reload the same page.
_currentPageURL = value;
NotifyPropertyChangedCurrentPageURL
}
}
// INotifyPropertyChanged implementations
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
// Store in static singleton instance of AppViewModel
public class App : Application
{
private static Lazy<AppViewModel> _ViewModel=new Lazy<ViewModel>();
public static AppViewModel ViewModel { get { return _ViewModel.Value; } }
....
public App()
{
AppViewModel.PropertyChanged=(s,a) =>
{
if (a.PropertyName=="CurrentPageURL")
{
NavigationService.Navigate(new Uri(AppViewModel.CurrentPageURL, UriKind.Relative));
};
}
}
}
// Usage sample
public class Page1ViewModel
{
private btnMoveNextPage_Click(object s, EventHandler a) {
App.ViewModel.CurrentURL="~/Page2.xaml";
}
}

Resources