Scrolling not smooth with ScrollView control in IOS devices - xamarin

I have been using SkiaSharp for a few month now and having great success on showing my drawing instructions on Android & IOS device. I am using SKCanvasView control which I am placing it inside ScrollView control. On scroll of ScrollView translating SKCanvasView to ScrollTop and ScrollLeft position so that SKCanvasView should intact with Visible region of ScrollView Control.
This is all working smoothly in Android. But in IOS device, scrolling is not smooth. Scrolling is holding by UI thread and it si not smooth. PLease have a look at the code and please suggest if you have any suggestions.
private void View_PaintSurface(object sender, SKPaintSurfaceEventArgs e)
{
SKRect rect = paginatedViewProvider.GetViewPortRect();
// I have lots of image tiles which I am drawing on skbitmap and returning it. This is taking around 10ms
SKBitmap pageBitmap = GenerateSKBitmapFromIntenseCalculation();
float Top = (float)(rect.Top - scrollTop);
float Left = (float)(rect.Left - scrollLeft);
e.Surface.Canvas.DrawBitmap(pageBitmap, SKRect.Create(Left, Top, rect.Width, rect.Height));
}
private bool OnTimerTick()
{
viewPanel.InvalidateSurface();
return true;
}

Related

Is there the equivelent of a Canvas control in Xamarin

I need to be able to do some direct drawing in Xamarin Forms. Is there a control equivelent to a canvas? I saw somewhere that maybe the AbsoluteLayout was, but I need something that I can, for example, execute a DrawString or DrawArc on. Does such a thing exist, or do you have to create a custom control?
Try with SkiaSharp, it basically allows you to do more or less the same than you can do in the native canvas of Android.
To use it:
Add the NuGet package SkiaSharp.Views.Forms to your projects.
Add the Skia namespace to your page or layout:
xmlns:skia="clr-namespace:SkiaSharp.Views.Forms;assembly=SkiaSharp.Views.Forms"
Add the Skia canvas view, defining the name of the callback method that will be used to draw on it from the code behind:
<skia:SKCanvasView
x:Name="CanvasView"
PaintSurface="OnCanvasViewPaintSurface"/>
Draw whatever you want:
private void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
var info = args.Info;
var surface = args.Surface;
var canvas = surface.Canvas;
canvas.Clear();
// In this example, we will draw a circle in the middle of the canvas
var paint = new SKPaint
{
Style = SKPaintStyle.Fill,
Color = Color.Red.ToSKColor(), // Alternatively: SKColors.Red
};
canvas.DrawCircle(info.Width / 2, info.Height / 2, 100, paint);
}

Xamarin.Forms Scrolling Grid With Relative Sizes

I have managed to create a GridView inside a ScrollView that uses 8 rows of row height 250 (Absolute). This was fine until I realised it would not work on screens with different resolutions. How can I create a grid view where there are 8 rows, but each row takes up half the screen so you have to scroll down to see the rest? Using 8 rows of height "*" just puts 8 rows onto the view with no scrolling. I am using xaml but can use c# if necessary.
You can access the device's screen height, so calculate 0.5 * height and set this as row height:
Create a static variable in app.xaml.cs
static public int ScreenHeight;
Then you need to set the value of your variable for iOS and Android.
Android: MainActivity.cs
protected override void OnCreate(Bundle bundle)
{
base.OnCreate (bundle);
global::Xamarin.Forms.Forms.Init(this, bundle);
App MyApp = new App();
App.ScreenHeight = (int)(Resources.DisplayMetrics.HeightPixels / Resources.DisplayMetrics.Density);
}
iOS: AppDelegate.cs
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
App.ScreenHeight = (int)UIScreen.MainScreen.Bounds.Height;
global::Xamarin.Forms.Forms.Init();
LoadApplication(new App());
return base.FinishedLaunching(app, options);
}
Now, have a property in the view's ViewModel which accesses ScreenHeight and calculate it to your needs
public int HalfScreenHeight
{
get { return App.ScreenHeight / 2; }
}
Finally, you can your row's height to HalfScreenHeight.
Important: If you allow device rotation for Portrait and LandScape mode on your page, you might need to update the binding according to your needs. You can access the screen's width in the same way.

UIScrollView's does not scroll AFTER rendering WKWebView content

Thank you for looking at my question. Currently, I have several input fields and a WKWebView embedded in a UIScrollView. Before any events fire, all the subviews fit inside the scroll view with no issue. I'm dynamically setting the WK's height based on document.body.scrollHeight which is captured in the DidFinishNavigation delegate located in WKNavigationDelegate. After the WK's height is set, the WK extends past the view-able content. Here's the code which I'm trying to force the scrollview to resize itself.
[Export("webView:didFinishNavigation:")] public async void DidFinishNavigation(WKWebView webView, WKNavigation navigation)
{
//get the webView's initial height
var initialHeight = webView.Frame.Height;
//get height of HTML's document.body.scrollHeight
var contentHeight = await GetContentHeight();
//create new frame for webview
CGRect newWebViewFrame = new CGRect(webView.Frame.X, webView.Frame.Y, webView.Frame.Width, contentHeight);
//set webview's frame
webView.Frame = newWebViewFrame;
//get the difference of webView's initial height and webView's current height
var differenceInHeight = contentHeight - initialHeight;
//create new cgrect and set the height to svMain's height + the difference in height of the HTML document
CGRect newScrollViewFrame = new CGRect(0, 0, svMainScroller.Frame.Width, svMainScroller.Frame.Height + differenceInHeight);
//set MainScroller's frame
svMainScroller.Frame = newScrollViewFrame;
//force scrolling
svMainScroller.ScrollEnabled = true;
//scrolling should be handled in the main scroller
webView.ScrollView.ScrollEnabled = false;
svMainScroller.SizeToFit();
}
The desired effect is to have the scroll view be able to scroll to the end of the newly defined height. Any tips on how would I go about doing that would be greatly appreciated.
Updating the frame of the scroll view was the problem. My guess is that if the frame is big enough to contain all of the contents, then there's no need to scroll. So, I updated the scroll view's ContentSize instead of updating its frame.
svMainScroller.ContentSize = new CGSize(View.Frame.Width, View.Frame.Height + differenceInHeight);
Also, as a side note, verify that wkwebview is added as a subview to the scroll view and not the main view.

How to use double buffering with SkiaSharp

I'm just playing around with Xamarin Forms and SkiaSharp. I've create a simple Xamarin forms ContentView with a SkiaCanvas. My PaintSurface is as follows to draw a simple green circle:
private void canvas_PaintSurface(object sender, SKPaintSurfaceEventArgs e)
{
var view = (SKCanvasView)sender;
var canvas = e.Surface.Canvas;
using(var paint = new SKPaint())
{
paint.Color = Colours.XamarinGreen;
paint.Style = SKPaintStyle.Fill;
paint.StrokeWidth = 1;
paint.IsAntialias = true;
canvas.DrawCircle((float)view.Width / 2.0f, (float)view.Height / 2.0f, (float)view.Height / 2.0f, paint);
}
}
In my test app, when I resize the window by dragging the corner of the window the circle flickers. I was wondering is there an easy way to enable double buffering when drawing graphics with SkiaSharp in a Xamarin.Forms app?

Xamarin Forms: scroll position in scrollview

Is there no possibility to read and set the position of the scrollview? For example, I would like to start the app with the scroll position at the bottom, not at the top.
You can get the X and Y position of a ScrollView from the ScrollX and ScrollY properties of the ScrollView object. Then you can set the position with the ScrollView method ScrollToAsync.
var x = scrollView.ScrollX;
var y = scrollView.ScrollY;
...
bool animate = false;
scrollView.ScrollToAsync(x, y, animate);
I've a better approach to this!
You can add this to your onAppearing() method:
protected override void OnAppearing()
{
base.OnAppearing();
/*Other stuff*/
viewModel.Messages.CollectionChanged += (sender, e) =>
{
var target = viewModel.Messages[viewModel.Messages.Count - 1];
ItemsListView.ScrollTo(target, ScrollToPosition.End, false);
};
}
This means that if your collection change, you get the target which is the last item (the one in the bottom) the you use scrollTo target, position end, with animation false.
when you get to the page it will open in the bottom, you don't see the scrolling because animation is false!
For Android, I have found a solution:
I write a custom renderer and in the OnDraw override Method, I call FullScroll(FocusSearchDirection.Down) the first time when the method is called.
What it makes easy, the Android ScrollViewRenderer is subtype of Android.Widget.ScrollView.
At the moment, I don't need an implementation for iOS, so I have no solution for iOS.

Resources