Xamarin.Forms SkiaSharp Gradient working on Android but not on iOS - ios

We need a Gradient on our pages, so we're using SkiaSharp and a GradientView. This works fine on Android but isn't working on iOS - the Gradient just doesn't display.
Do we need to do any iOS-specific initialisation for SkiaSharp on iOS, with renderers or code in the AppDelegate?
Edit This is running on an iPad with iOS 10.3.3.
Our XAML:
<?xml version="1.0" encoding="utf-8" ?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MyProject.XForms.Controls.GradientView">
</ContentView>
Our XAML.cs
using SkiaSharp;
using SkiaSharp.Views.Forms;
using Xamarin.Forms;
namespace MyProject.XForms.Controls
{
public partial class GradientView : ContentView
{
public Color StartColor { get; set; } = Color.Transparent;
public Color EndColor { get; set; } = Color.Transparent;
public bool Horizontal { get; set; } = false;
public GradientView()
{
InitializeComponent();
SKCanvasView canvasView = new SKCanvasView();
canvasView.PaintSurface += OnCanvasViewPaintSurface;
Content = canvasView;
}
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
var colors = new SKColor[] { StartColor.ToSKColor(), EndColor.ToSKColor() };
SKPoint startPoint = new SKPoint(0, 0);
SKPoint endPoint = Horizontal ? new SKPoint(info.Width, 0) : new SKPoint(0, info.Height);
var shader = SKShader.CreateLinearGradient(startPoint, endPoint, colors, null, SKShaderTileMode.Clamp);
SKPaint paint = new SKPaint
{
Style = SKPaintStyle.Fill,
Shader = shader
};
canvas.DrawRect(new SKRect(0, 0, info.Width, info.Height), paint);
}
}
}

I ran into the same problem and read the documentation. It says you need to tell the canvas to update. You do this using the following line of code:
canvasView.InvalidateSurface();

Maybe you haven't installed the NuGets (both SkiaSharp and SkiaSharp.Views.Forms) into the iOS project?
I copy-pasted your code into a brand new app, and it worked first time.
Some other things that may be the issue is that the view height/width is 0. Or, the dodgy case where the color is transparent.

Related

Forcing specific MAUI view to Landscape Orientation using MultiTargeting Feature working for Android but not iOS

I need a specific MAUI page to be in Landscape only orientation.
I found this tutorial about forcing device orientation and I am using the multi-targeting feature of MAUI to implement the device specific code needed to force this orientation. The tutorial says that they didn't test the iOS version.
I have the tutorial working for Android (allows programmatic forcing of Landscape orientation for a single page through a singleton service) but not for iOS.
using System;
namespace ScoreKeepersBoard.DeviceServices;
public partial class DeviceOrientationService : IDeviceOrientationService
{
public partial void SetDeviceOrientation(DisplayOrientation displayOrientation);
}
Here is where I inject my device orientation service into my view model and set the orientation to landscape:
public partial class NewGameViewModel : ObservableObject
{
IGameTypeDataAccess gameTypeDataAccess;
ITeamDataAccess teamDataAccess;
IDeviceOrientationService deviceOrientationService;
[ObservableProperty]
IList<GameType> gameTypes = new List<GameType>();
[ObservableProperty]
private GameType selectedGameType;
[ObservableProperty]
private string gameTypeSelectionError;
[ObservableProperty]
private ObservableCollection<Team> teamOneTeams = new ObservableCollection<Team>();
[ObservableProperty]
private Team teamOneSelection;
[ObservableProperty]
private string teamOneSelectionError;
[ObservableProperty]
private ObservableCollection<Team> teamTwoTeams = new ObservableCollection<Team>();
[ObservableProperty]
private Team teamTwoSelection;
[ObservableProperty]
private string teamTwoSelectionError;
private ObservableCollection<Team> allTeams = new ObservableCollection<Team>();
private bool react = true;
public NewGameViewModel(IGameTypeDataAccess iGameTypeDataAccess, ITeamDataAccess iTeamDataAccess, IDeviceOrientationService iDeviceOrientationService)
{
gameTypeDataAccess = iGameTypeDataAccess;
teamDataAccess = iTeamDataAccess;
deviceOrientationService = iDeviceOrientationService;
deviceOrientationService.SetDeviceOrientation(DisplayOrientation.Landscape);
}
}
And here is my multi targeted code in the /Platforms/Android folder:
using System;
using Android.Content.PM;
namespace ScoreKeepersBoard.DeviceServices;
public partial class DeviceOrientationService
{
private static readonly IReadOnlyDictionary<DisplayOrientation, ScreenOrientation> _androidDisplayOrientationMap =
new Dictionary<DisplayOrientation, ScreenOrientation>
{
[DisplayOrientation.Landscape] = ScreenOrientation.Landscape,
[DisplayOrientation.Portrait] = ScreenOrientation.Portrait,
};
public partial void SetDeviceOrientation(DisplayOrientation displayOrientation)
{
var currentActivity = ActivityStateManager.Default.GetCurrentActivity();
if(currentActivity is not null)
{
if(_androidDisplayOrientationMap.TryGetValue(displayOrientation, out ScreenOrientation screenOrientation))
{
currentActivity.RequestedOrientation = screenOrientation;
}
}
}
}
I have similar setup for multi-targeted to iOS in /Platforms/iOS.
UPDATE: I Edited my code according to the answer from Dongzhi Wang-MSFT
using System;
using Foundation;
using UIKit;
namespace ScoreKeepersBoard.DeviceServices;
public partial class DeviceOrientationService
{
private static readonly IReadOnlyDictionary<DisplayOrientation, UIInterfaceOrientation> _iosDisplayOrientationMap =
new Dictionary<DisplayOrientation, UIInterfaceOrientation>
{
[DisplayOrientation.Landscape] = UIInterfaceOrientation.LandscapeLeft,
[DisplayOrientation.Portrait] = UIInterfaceOrientation.Portrait,
};
public partial void SetDeviceOrientation(DisplayOrientation displayOrientation)
{
if (UIDevice.CurrentDevice.CheckSystemVersion(16, 0))
{
var scene = (UIApplication.SharedApplication.ConnectedScenes.ToArray()[0] as UIWindowScene);
if (scene != null)
{
var uiAppplication = UIApplication.SharedApplication;
var test = UIApplication.SharedApplication.KeyWindow?.RootViewController;
if (test != null)
{
test.SetNeedsUpdateOfSupportedInterfaceOrientations();
scene.RequestGeometryUpdate(
new UIWindowSceneGeometryPreferencesIOS(UIInterfaceOrientationMask.Portrait), error => { });
}
}
}
else
{
UIDevice.CurrentDevice.SetValueForKey(new NSNumber((int)UIInterfaceOrientation.Portrait), new NSString("orientation"));
}
}
}
This forces the orientation to Portrait but when I switch from Portrait to Landscape the layout first switches to Landscape and then gets forced into Portrait as shown in the GIF image below.
How can I KEEP it in Portrait as the user changes the orientation?
UPDATE:
I updated my .NET MAUI and the update required me to use XCODE 14.2 and now my virtual emulators are all running iOS 16.2 and now the iOS version of the code doesn't work at all and doesn't lock the screen into any orientation.
I get this warning now in the iOS platform specific code:
It looks like for iOS version 16.2 this solution doesn't work anymore!
In Maui, you can use Invoke platform code to call the iOS native API to achieve. For details, please refer to the official documentation: Invoke platform code | Microsoft.
For iOS part of the code:
if (UIDevice.CurrentDevice.CheckSystemVersion(16, 0))
{
var scene = (UIApplication.SharedApplication.ConnectedScenes.ToArray()[0] as UIWindowScene);
if(scene != null)
{
var test = UIApplication.SharedApplication.KeyWindow?.RootViewController;
if (test != null)
{
test.SetNeedsUpdateOfSupportedInterfaceOrientations();
scene.RequestGeometryUpdate(
new UIWindowSceneGeometryPreferencesIOS(UIInterfaceOrientationMask.Landscape), error => { });
}
}
}
else
{
UIDevice.CurrentDevice.SetValueForKey(new NSNumber((int)UIInterfaceOrientation.LandscapeLeft), new NSString("orientation"));
}

Show ProgressBar programattically while user logging on

I found this class at Centering ProgressBar Programmatically in Android which would display a progressbar programmatically, problem is it's an Xamarin Android Studio example and I'm trying to convert it to Xamarin for Visual Studio 2017. This is the code that I have successfully converted with those lines that I can't seem to find a Xamarin VS 2017 equivalent for.
using System.Text;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
namespace OML_Android
{
class ProgressBarHandler
{
private ProgressBar mProgressBar;
private Context mContext;
public ProgressBarHandler(Context context)
{
mContext = context;
ViewGroup layout = (ViewGroup)((Activity)context).FindViewById(Android.Resource.Id.Content).RootView;
mProgressBar = new ProgressBar(context, null, Android.Resource.Attribute.ProgressBarStyleLarge);
// there is no setIndeterminate method for progressbar
mProgressBar.setIndeterminate(true);
// I cannot find an equivilent for LayoutParams
RelativeLayout.LayoutParams params = RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MatchParent, RelativeLayout.LayoutParams.MatchParent);
RelativeLayout rl = new RelativeLayout(context);
// No equivalent for Gravity.CENTER
rl.SetGravity(Gravity.CENTER);
rl.AddView(mProgressBar);
layout.AddView(rl, params);
hide();
}
public void show()
{
mProgressBar.Visibility = Android.Views.ViewStates.Visible;
}
public void hide()
{
mProgressBar.Visibility = Android.Views.ViewStates.Invisible;
}
}
}
Once I have this converted and working I want it to overlay my logon view until the view finishes processing.
I help you transform Java code to C#, there is running GIF.
There is code.
class ProgressBarHandler
{
private ProgressBar mProgressBar;
private Context mContext;
public ProgressBarHandler(Context context)
{
mContext = context;
ViewGroup layout = (ViewGroup)((Activity)context).FindViewById(Android.Resource.Id.Content).RootView;
mProgressBar = new ProgressBar(context, null, Android.Resource.Attribute.ProgressBarStyleLarge);
// there is no setIndeterminate method for progressbar
// mProgressBar.SetIndeterminate(true);
mProgressBar.Indeterminate = true;
// I cannot find an equivilent for LayoutParams
RelativeLayout.LayoutParams layoutparams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MatchParent, RelativeLayout.LayoutParams.MatchParent);
RelativeLayout rl = new RelativeLayout(context);
// No equivalent for Gravity.CENTER
rl.SetGravity(GravityFlags.Center );
rl.AddView(mProgressBar);
layout.AddView(rl, layoutparams);
hide();
}
public void show()
{
mProgressBar.Visibility = Android.Views.ViewStates.Visible;
}
public void hide()
{
mProgressBar.Visibility = Android.Views.ViewStates.Invisible;
}
}
You can use it directly like following code in Activity.
var progress= new ProgressBarHandler(this);
progress.show();

Xamarin Android TypefaceSpan with custom font in Assets folder

As the title states, I need to use a TypefaceSpan object with a custom font present inside the Assets but I can't find the correct way to achieve this.
The file of the font is "HelveticaNeueLTCom-BdCn.ttf"
These are my two attempt which did not work for me:
// first attempt
var textViewTitle = new TextView(Context);
var span = new SpannableString("MyLongTitle");
span.SetSpan(new TypefaceSpan("HelveticaNeueLTCom-BdCn.ttf"), 0, 5, SpanTypes.ExclusiveExclusive);
textViewTitle.TextFormatted = span;
// second attempt
var textViewTitle = new TextView(Context);
var span = new SpannableString("MyLongTitle");
span.SetSpan(new Typeface(Typeface.CreateFromAsset(Context.Assets, "fonts/HelveticaNeueLTCom-BdCn.ttf")), 0, 5, SpanTypes.ExclusiveExclusive);
textViewTitle.TextFormatted = span;
Anyone has some hints or advice?
Almost one year later but I faced the same problem and I found this way of achieving it.
I saw you posted the same question in the Xamarin forum here but the link you wrote in your answer is broken. However, I think that it is this post that helped you as it helped me.
So here the way to go.
First create a custom TypefaceSpan like this
using System;
using Android.OS;
using Android.Runtime;
using Android.Text.Style;
using Android.Graphics;
using Android.Text;
namespace Droid.Spans
{
public class CustomTypefaceSpan : TypefaceSpan
{
private readonly Typeface _typeface;
public CustomTypefaceSpan(Typeface typeface)
: base(string.Empty)
{
_typeface = typeface;
}
public CustomTypefaceSpan(IntPtr javaReference, JniHandleOwnership transfer)
: base(javaReference, transfer)
{
}
public CustomTypefaceSpan(Parcel src)
: base(src)
{
}
public CustomTypefaceSpan(string family)
: base(family)
{
}
public override void UpdateDrawState(TextPaint ds)
{
ApplyTypeface(ds, _typeface);
}
public override void UpdateMeasureState(TextPaint paint)
{
ApplyTypeface(paint, _typeface);
}
private static void ApplyTypeface(Paint paint, Typeface tf)
{
paint.SetTypeface(tf);
}
}
}
Then use this CustomTypefaceSpan to add a span to the SpannableString
var spannableString = new SpannableString("Anything to write with a special font");
spannableString.SetSpan(new CustomTypefaceSpan(Typeface.CreateFromAsset(Assets, "HelveticaNeueLTCom-BdCn.ttf"), 0, 7, SpanTypes.InclusiveInclusive);
textView.TextFormatted = spannableString;

Convert StackLayout as Image in Xamarin

I'm working on Xmarin Forms(PCL) project, I want to convert the StackLayout to Image / buffer and send it to printer for hard print.
Can anyone suggest how to do it in (Xamarin.Android & Xamarin.iOS).
You can't. Xamarin does not have that kind of feature. You should write a Renderer for your UIComponent.
Fortunately there is an Objective-C iOS implementation, and an Android one as well. You can inspire from them.
Taken from this link, which I have personally used, quite a while back though, the following code will take a screenshot of the entire page.
I ended up modifying the code to only take a screenshot of a specific view on the page and also changed a few other things but this example is what I based it off of, so let me know if you would rather see that code and/or if something below is not working for you.
First you create an interface in your Forms project, IScreenshotManager.cs for example:
public interface IScreenshotManager {
Task<byte[]> CaptureAsync();
}
Now we need to implement our interface in Android, ScreenshotManager.cs for example:
public class ScreenshotManager : IScreenshotManager {
public static Activity Activity { get; set; }
public async System.Threading.Tasks.Task<byte[]> CaptureAsync() {
if(Activity == null) {
throw new Exception("You have to set ScreenshotManager.Activity in your Android project");
}
var view = Activity.Window.DecorView;
view.DrawingCacheEnabled = true;
Bitmap bitmap = view.GetDrawingCache(true);
byte[] bitmapData;
using (var stream = new MemoryStream()) {
bitmap.Compress(Bitmap.CompressFormat.Png, 0, stream);
bitmapData = stream.ToArray();
}
return bitmapData;
}
}
Then set ScreenshotManager.Activity in MainActivity:
public class MainActivity : Xamarin.Forms.Platform.Android.FormsApplicationActivity {
protected override async void OnCreate(Android.OS.Bundle bundle) {
...
ScreenshotManager.Activity = this; //There are better ways to do this but this is what the example from the link suggests
...
}
}
Finally we implement this on iOS, ScreenshotManager.cs:
public class ScreenshotManager : IScreenshotManager {
public async System.Threading.Tasks.Task<byte[]> CaptureAsync() {
var view = UIApplication.SharedApplication.KeyWindow.RootViewController.View;
UIGraphics.BeginImageContext(view.Frame.Size);
view.DrawViewHierarchy(view.Frame, true);
var image = UIGraphics.GetImageFromCurrentImageContext();
UIGraphics.EndImageContext();
using(var imageData = image.AsPNG()) {
var bytes = new byte[imageData.Length];
System.Runtime.InteropServices.Marshal.Copy(imageData.Bytes, bytes, 0, Convert.ToInt32(imageData.Length));
return bytes;
}
}
}

Change "Active" icon on selected NavigationPage when using TabbedPage

I'm very, very new to Xamarin.Forms. My task, if it is possible, and I'm not sure if it is, is to change our icon from the default blue when it is active.
I was given icons that are orange and they would like to display those or at least the color instead of the default blue. Again, I'm not sure if this is possible.
This is the code I'm using for the tabbed page.
public class LandingPage : TabbedPage
{
public LandingPage ()
{
NavigationPage homepage = new NavigationPage (new CarouselPage {
Title = "Title",
Children = {
//code removed
}
});
NavigationPage eventspage = new NavigationPage (new ContentPage {
Title = "Calendar Event List",
Content = new EventList ()
});
NavigationPage morepage = new NavigationPage (new MorePage ());
homepage.BarBackgroundColor = Device.OnPlatform (Color.FromHex (DependencyService.Get<IContentStrings>().BarBackgroundColor), Color.Transparent, Color.Transparent);
homepage.BarTextColor = Color.FromHex(DependencyService.Get<IContentStrings>().BarTextColor);
homepage.Title = DependencyService.Get<IContentStrings>().HomeTitle;
homepage.Icon = DependencyService.Get<IContentStrings>().HomeImage;
eventspage.BarBackgroundColor = Device.OnPlatform (Color.FromHex (DependencyService.Get<IContentStrings>().BarBackgroundColor), Color.Transparent, Color.Transparent);
eventspage.BarTextColor = Color.FromHex(DependencyService.Get<IContentStrings>().BarTextColor);
eventspage.Title = DependencyService.Get<IContentStrings>().EventTitle;
eventspage.Icon = DependencyService.Get<IContentStrings>().EventImage;
morepage.BarBackgroundColor = Device.OnPlatform (Color.FromHex (DependencyService.Get<IContentStrings>().BarBackgroundColor), Color.Transparent, Color.Transparent);
morepage.BarTextColor = Color.FromHex(DependencyService.Get<IContentStrings>().BarTextColor);
morepage.Title = DependencyService.Get<IContentStrings>().MoreTitle;
morepage.Icon = DependencyService.Get<IContentStrings>().MoreImage;
Children.Add (homepage);
Children.Add (eventspage);
Children.Add (morepage);
}
}
I'm not sure if I'm able to use a custom renderer or anything. I do not know if I have any options and any guidance is greatly appreciated!
You can set the active tab icon color with a simple custom iOS renderer like this:
[assembly: ExportRenderer(typeof(TabbedPage), typeof(MyTabbedPageRenderer))]
namespace MyApp.iOS.Renderers
{
public class MyTabbedPageRenderer : TabbedRenderer
{
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
TabBar.TintColor = UIColor.Orange;
}
}
}
I found was finally able to find the answer after searching the internet a few hours and then coming back to the app on a different day. To change the default from the blue, I changed the UITabbar tint color in the AppDelegate.
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
UIApplication.SharedApplication.SetStatusBarStyle (UIStatusBarStyle.LightContent, false);
global::Xamarin.Forms.Forms.Init ();
LoadApplication (new App ());
//this changes the default iOS tintcolor for the icon when it's activated
UITabBar.Appearance.TintColor = UIColor.FromRGB(223, 112, 13);
return base.FinishedLaunching (app, options);
}

Resources