Pages

Friday, 11 January 2019

Taking a Screenshot in Xamarin.Forms

There may be a few occasions when you want to take a screenshot of your app, such as for sending to support. Xamarin.Forms doesn’t have this functionality; hence we will have to create a custom renderer for iOS and Android to capture the screen.

Interface and Dependency Injection

If we want to use this in our class library, we will need to create an interface to use, then use DependencyService or another IoC framework. If you are using a Shared project, you won’t need this. See Dependency Injection, if you require more information on this pattern.
public interface IScreenshotService
{
    byte[] Capture();
}

Android

Place this code, in your Android project.
public namespace MyMobileApp
{
    public class ScreenshotService : IScreenshotService
    {
        private readonly Activity _currentActivity;
        public void SetActivity(Activity activity) => _currentActivity = activity;

        public byte[] Capture()
        {
            var rootView = _currentActivity.Window.DecorView.RootView;

            using (var screenshot = Bitmap.CreateBitmap(
                                    rootView.Width,
                                    rootView.Height,
                                    Bitmap.Config.Argb8888))
            {
                var canvas = new Canvas(screenshot);
                rootView.Draw(canvas);

                using (var stream = new MemoryStream())
                {
                    screenshot.Compress(Bitmap.CompressFormat.Png, 90, stream);
                    return stream.ToArray();
                }
            }
        }
    }
}
In your MainActivity.cs, add in the following code to register your service. In here, we need to pass through the current activity.
DependencyService.Register<ScreenshotService>();

DependencyService.Get<ScreenshotService>().SetActivity(this);

iOS

Place this code in your iOS project.
[assembly: Dependency(typeof(ScreenshotService))]
public namespace MyMobileApp 
{
    public class ScreenshotService : IScreenshotService
    {
        public byte[] Capture()
        {
            var capture = UIScreen.MainScreen.Capture();
            using (NSData data = capture.AsPNG())
            {
                var bytes = new byte[data.Length];
                Marshal.Copy(data.Bytes, bytes, 0, Convert.ToInt32(data.Length));
                return bytes;
            }
        }
    }
}

Usage

Now you can use this service anywhere in your app. Simply use this code to get an instant screenshot of your app.
var screenshotData = DependencyService.Get<IScreenshotService>().Capture();
You can then either save this to file storage.

If you want to display the screenshot, you can assign it to a property in your ViewModel.
private byte[] _image;
public byte[] Image
{
    get => _image;
    set
    {
        _image = value;
       OnPropertyChanged(); // Or whatever MVVM framework method you might use.
    }
}

Then use a ByteToImageConverter.
public class BytesToImageConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value == null)
            return null;

        return ImageSource.FromStream(() => new MemoryStream((byte[])value));
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => throw new NotImplementedException();
}
To finally display your image in XAML, add the converter to your resource dictionary.
<ContentPage.Resources>
    <ResourceDictionary>
        <converters:BytesToImageConverter x:Key="BytesToImage" />
    </ResourceDictionary>
</ContentPage.Resources>
Then use with an image, bound to your ViewModel property.
<Image Source="{Binding Image, Converter={StaticResource BytesToImage}}" Aspect="AspectFill" />

1 comment: