Forms9Patch: Simplify multi-device image management in your PCL Xamarin.Forms mobile apps

BuildCalcBuildCalc Ben AskrenUSMember ✭✭✭

Announcement of Form9Patch

Xamarin Forms is great for developing apps on Android and iOS but it is missing two important tools for developers: scalable images and PCL multi-screen image management. Android developers use NinePatch bitmaps and the drawable directory naming convention for this purpose. Likewise, iOS developers use ResizeableImageWithCapInsets and the @2x, @3x, @4x file naming convention for this purpose.

Forms 9 Patch enhances Xamarin Forms to enable multi-resolution / multi-screen image management to PCL apps for iOS and Android.

What is it?

Simply stated, Forms9Patch is two separate elements (Image and ImageSource) which are multi-screen / multi-resolution extensions of their Xamarin Forms counterparts.

Forms9Patch.ImageSource

Xamarin Forms provides native iOS and Android multi-screen image management (described here). This requires storing your iOS images using the native iOS schema and storing your Android images using the Android schema. In other words, duplicative efforts to get the same results on both Android and iOS. Forms9Patch.ImageSource extends Xamarin.Forms.ImageSource capabilities to bring multi-screen image management to your PCL assemblies - so you only have to generate and configure your app's image resources once. Forms9Patch.ImageSource is a cross-platform implementation to sourcing multi-screen images in Xamarin Forms PCL apps as embedded resources.

Forms9Patch.Image

Forms9Patch.Image compliments Xamarin.Forms.Image to provide Xamarin Forms with a scaleable image element. Scalable images are images that fill their parent view by stretching in designated regions. The source image for the Forms9Patch.Image element can be specified either as a Forms9Patch.ImageSource or a Xamarin.Forms.ImageSource. Supported file formats are NinePatch (.9.png), .png, .jpg, .jpeg, .gif, .bmp, and .bmpf.

Example code

After adding the file bubble.9.png to your PCL project assembly as an EmbeddedResource, you can display it using something like the following:

var bubbleImage = new Forms9Patch.Image () {
    Source = ImageSource.FromResource("MyDemoApp.Resources.bubble.9.png"),
    HeightRequest = 110,
}
var label = new label () {
    Text = "Forms9Path NinePatch Image",
    HorizontalOptions = LayoutOptions.Center,
}

Example XAML

In Xamarin Forms, access to embedded resources from XAML requires some additional work. Unfortunately, Forms9Patch is no different. As with Xamarin Forms, you will need (in the same assembly as your embedded resource images) a simple custom XAML markup extension to load images using their ResourceID.

    [ContentProperty ("Source")]
    public class ImageMultiResourceExtension : IMarkupExtension
    {
        public string Source { get; set; }

        public object ProvideValue (IServiceProvider serviceProvider)
        {
            if (Source == null)
                return null;

            // Do your translation lookup here, using whatever method you require
            var imageSource = Forms9Patch.ImageSource.FromMultiResource(Source);

            return imageSource;
        }
    }

Once you have the above, you can load your embedded resource images as shown in the below example. Be sure to add a namespace for the assembly that contains both your MarkupExtension and your EmbeddedResources (local in the below example).

<?xml version="1.0" encoding="UTF-8"?>
<ContentPage
    xmlns="http://xamarin.com/schemas/2014/forms"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
    xmlns:local="clr-namespace:MyXamlDemo;assembly=MyXamlDemo"
    x:Class="MyXamlDemo.MyPage"
    Padding="5, 20, 5, 5">
    <ScrollView>
        <ScrollView.Content>
            <StackLayout>
            <Label Text="Xamarin.Image"/>
            <Image Source="{local:ImageMultiResource Forms9PatchDemo.Resources.image}"/>
            </StackLayout>
        </ScrollView.Content>
    </ScrollView>
</ContentPage>

Where to learn more

Project page: http://Forms9Patch.com
Nuget page: https://www.nuget.org/packages/Forms9Patch/0.9.1
Demo app repository: https://github.com/baskren/Forms9PatchDemo

«13

Posts

  • BuildCalcBuildCalc Ben Askren USMember ✭✭✭

    Version 0.9.5 (https://www.nuget.org/packages/Forms9Patch) is now available. This release adds the following functionality:

    1. Forms9Patch.Image now supports AspectFill, AspectFit, Fill and Tile fills when the image is not a scalable image (not NinePatch or CapInsets is not set).
    2. new Forms9Patch.ContentView extends Xamarin.Forms.ContentView by adding the ability to use a Forms9Patch.Image as BackgroundImage!
    3. new Forms9Patch.Frame extends Xamarin.Forms.Frame by:
      • adding the ability to use a Forms9Patch.Image as BackgroundImage!
      • adding the OutlineWidth and OutlineRadius properties (from when a BackgroundImage is not available)
  • BuildCalcBuildCalc Ben Askren USMember ✭✭✭

    Version 0.9.7 is now available with some great enhancements:

    • Forms9Patch.Image now has tinting via the TintColor property.
    • The Forms9Patch versions of ContentView, Frame, AbsoluteLayout, Grid, RelativeLayout, and StackLayout support using a Forms9Patch.Image as a background - which means it can be NinePatch scaled, aspect filled, aspect fitted, stretched to fit, or tiled.
    • The Forms9Patch versions of Frame, AbsoluteLayout, Grid, RelativeLayout, and StackLayout support a background outline with color, width and radius properties. If the BackgroundColor property is set, the HasShadow works (on Android, too) and the ShadowInverse property gives it a recessed appearance.
    • Forms9Patch.ImageButton: an image button that can change the background image, foreground image, text, and text formatting depending on button state (default, selected, disabled, disabled+selected).
    • Forms9Patch.MaterialButton: a convenience element, loosely based on the Material design language, with foreground image and text. Here are some examples:

      MaterialButton examples

    Also, I should have noted a long time ago that the Forms9Patch.ImageSource, and the non-image portions of Frame, AbsoluteLayout, Grid, RelativeLayout, StackLayout, and MaterialButton are free to use without a license.

    You can learn more and see examples at http://Forms9Patch.com

  • BuildCalcBuildCalc Ben Askren USMember ✭✭✭

    Version 0.9.8.1 is now available and has an enhancement I've very proud of: MaterialSegmentedControl. This element is segmented button element (like iOS's UISegmentedControl) with a great deal more flexibility:

    • vertical and horizontal orientation
    • segments can have image and labels in vertical and horizontal orientation
    • control of outline (color, width, radius)
    • shadow works on Android
    • control of separator width

    Take a look at http://Forms9Patch.com for documentation and examples.

  • BuildCalcBuildCalc Ben Askren USMember ✭✭✭

    Version 0.9.9.3 is now available. All button response times and Forms9Patch.ImageSource.FromMultiResource and Forms9Patch.ImageSource.FromResource image loading times are as good (and some cases better) than their Xamarin.Forms counterparts. ImageButton now has the PressingState - which allows you to define the appearance of the button while it is being pressed by the user. All the buttons (including the MaterialSegmentedControl) have the LongPressing and LongPressed events.

  • BuildCalcBuildCalc Ben Askren USMember ✭✭✭

    Version 0.9.10 is now available and it has a very special addition - Modal and Bubble popups!

    GIF of Modal Popup

    GIF of Bubble Popup on iPhone

    GIF of Bubble Popup on Android

    These automatically place the popup over the top most Xamarin.Forms.Page (unless you chose another page). Choosing the target of a Forms9Patch.BubblePopup is just of matter of setting the Target property to a Xamarin.Forms.View.

    There's a lot more information about them at http://Forms9Patch.com

  • AlanSpiresAlanSpires Alan Spires USBeta ✭✭

    Do CapInsets work on Droid as well? I have a solution that is working perfectly on iOS but with android it is doing some weird stuff.

  • BuildCalcBuildCalc Ben Askren USMember ✭✭✭

    @AlanSpires

    Yes, it should work on Droid just as well (it is used in the demo app as such).

                            new Forms9Patch.Image {
                                Source = ImageSource.FromUri(new Uri("http://buildcalc.com/forms9patch/demo/redribbon.png")),
                                CapInsets = new Thickness(23,0,111,0),
                            },
    
    

    That being said, there is a chance you've tripped over a bug. Can you share with me a sample project that demonstrates what you are seeing?

    • Ben
  • Matthew.4307Matthew.4307 Matthew USMember ✭✭✭

    The Modal and Bubble popups look very interesting and I could definitely use them, but only once there is Windows 8.1 (desktop) support because the app needs to support Android, iOS and Windows RT.

  • AlanSpiresAlanSpires Alan Spires USBeta ✭✭

    I don't have time right this second for a repo but here are some images showing what I'm seeing.

    Image before a simple up - down scroll
    https://dropbox.com/s/hqo5kabi4qgyclg/Before%20Scroll.png?dl=0

    Image after a up - down scroll
    https://dropbox.com/s/kp7fgb2253njc9d/After%20Scroll.png?dl=0

    Not sure what's happening there.

    Thanks!

  • BuildCalcBuildCalc Ben Askren USMember ✭✭✭

    @Matthew.4307

    Thanks for letting me know. I'll be working on that later this spring.

  • BuildCalcBuildCalc Ben Askren USMember ✭✭✭
    edited March 2016

    @AlanSpires -

    In your example, are you using a ListView or a StackLayout in a ScrollView?

  • AlanSpiresAlanSpires Alan Spires USBeta ✭✭

    Listview with item template selector for left and right cells

  • BuildCalcBuildCalc Ben Askren USMember ✭✭✭

    @AlanSpires , can you send me a copy of the template?

  • AlanSpiresAlanSpires Alan Spires USBeta ✭✭

    The template selector is nothing special. To send you the cells would be a lil tricky with the dependencies and what not.

  • AlanSpiresAlanSpires Alan Spires USBeta ✭✭

    I emailed you the cell, should be able to strip out the dependencies without a issue

  • BuildCalcBuildCalc Ben Askren USMember ✭✭✭

    Got it.

  • BuildCalcBuildCalc Ben Askren USMember ✭✭✭

    @AlanSpires, I've updated the Nuget package to 0.9.10.1 with what I believe is a fix (or at least a fix to the problem I could create). Also, I've updated the github demo (https://github.com/baskren/Forms9PatchDemo) to include a mockup of a chat app using a nine-patch image as the background for a Forms9Patch.ContentView inside of cells in a Xamarin.Forms.ListView.

  • EverettEverett Everett Whiteway USMember ✭✭
    edited March 2016

    First, nice package! However I'm running into an issue with referencing a 9patch image twice on Android (haven't tested iOS) by calling Forms9Patch.ImageSource.FromResource. The second time I call the graphic, the image seems to just stretch the image, ignoring the 9patches. Any chance the cached image from the "FromResource" is stripping the 9patch markings?

    Running Forms9Patch version 0.9.10.3
    Also forms9patch.com seems to be down.
    Thanks!

  • EverettEverett Everett Whiteway USMember ✭✭
    edited March 2016

    Here is the issue I'm running into
    and below is the code that I'm using in a simple XF Portable Project

    public App()
    {
        MainPage = new ContentPage
        {
            Content = new StackLayout
            {
                VerticalOptions = LayoutOptions.Center,
                Children = {
                    CreateBar(0.5),
                    CreateBar(0.75),
                    CreateBar(0.65)
                }
            }
        };
    }
    
    private View CreateBar(double value)
    {
        AbsoluteLayout layout = new AbsoluteLayout() { BackgroundColor = Color.FromHex("#881f29") };
        var whiteBar = new Forms9Patch.Image() { Source = Forms9Patch.ImageSource.FromMultiResource("_9PatchTest.Resources.WhiteBar"), Fill = Forms9Patch.Fill.Fill };
        layout.Children.Add(whiteBar, new Rectangle(0, 0.5, value, 1), AbsoluteLayoutFlags.All);
        layout.HeightRequest = 120;
        return layout;
    }
    

    The rest is just the standard template code for a portable project.
    As you can see, one time, it seems to properly do stretch according to the 9patch, but every other time it appears to fail.
    I've also tried
    Source = ImageSource.FromResource("_9PatchTest.Resources.WhiteBar.9.png")
    to identical results

  • BuildCalcBuildCalc Ben Askren USMember ✭✭✭

    @Everett -

    First, thanks for trying out Forms9Patch! I've got a lot of myself in it so it means a lot to me when someone sees value in it.

    What has happened is you've tried using Forms9Patch without a valid license key. When that happens, Forms9Patch will continue to work BUT it will only do it's image manipulations or caching on the first image presented to it. After that, it falls back to Xamarin.Forms.Image (which gives the results you see).

    The fix? Either get a valid license key or change your app name (temporarily) to Forms9PatchDemo (see instructions in the FAQ section of Forms9Patch.com on how to do this).

    And thanks again for giving Forms9Patch a try!

    Ben

  • BuildCalcBuildCalc Ben Askren USMember ✭✭✭
    edited March 2016

    I've just updated the ChatListView demo to use the new (as of Xamarin Forms 2.1) DataTemplateSelector approach to dynamically selecting the appropriate template for a ListView. Here are some screen shots of the demo in action:

    iOS ChatListView Android ChatListView

  • MichaelSattlerMichaelSattler Michael Sattler USMember

    Hi Ben,

    your addition of the two Popup types looks really great! I'd love to use the lib in my current project, because my customer wants me to implement context menus for the cells in a ListView - which I bet is a royal pain to implement from scratch, especially given the complex view structure of my app.

    And that view structure is most likely the cause of the problem I'm facing: I can't get the BubblePopup to display.

    To be exact, I did manage to display it inside a rather simple page of my app: The target for the popup is a Button inside a ContentPage inside a NavigationPage. That works well.
    But the structure of the page I actually want to display the popup in is:

    A ListView inside a ContentPage inside a NavigationPage inside a MasterDetailPage inside a TabbedPage.

    And the goal is to use one of the cells inside the ListView as the target for the popup.
    So far I've tried creating and showing the BubblePopup without passing a host Page and also passing the ListView's parent ContentView to the popup. Unfortunately, nothing has worked so far. Neither the popup nor its background shroud will display.

    Have you got any hints for me how I need to create and configure the popup so it's properly displayed? Can it handle a Page structure as complex as mine at all?

    Currently my code for displaying the popup looks like this:

            public void BubblePopup (VisualElement origin, Page parentPage = null)
            {
                var hostPage = parentPage ?? Application.Current.MainPage;
    
                var popup = new Forms9Patch.BubblePopup(origin, parentPage) {
                    PointerDirection = Forms9Patch.PointerDirection.Vertical,
                    Content = new StackLayout {
                        Children = {
                            new Label { Text = "iGudeWie" }
                        }
                    }
                };
                popup.IsVisible = true;
            }
    

    The page I'm passing is the ListView's ContentPage.

  • BuildCalcBuildCalc Ben Askren USMember ✭✭✭

    @MichaelSattler ,

    It should be able to handle the complex page structure (I've been testing it against structures as complex). Assuming that is the case, read on ...

    I may be off base but I'm wondering if it has something to do with the VisualElement you are passing to it. If the element is as large as the page (or close to it), then it will have no choice but to render itself off the page. A good test would be to try your BubblePopup with a PointerDirection.None. If I remember correctly, that will effectively make it act like a ModalPopup. If that works, then it is an issue of not having enough room to render.

    Something else you may have already considered. If you're wanting to point to a cell (or a VisualElement in a cell) in the ListView, it's pretty tough to get a VisualElement for a ListView cell or item. This is one of a few actions I'm off making easier for myself in a Drag and Drop listview that I'm building. Effectively, what I'm doing is adding a weak reference to the VisualElement that is generated for the cell's content in the ListView's DataTemplateSelector. I do this in the class(es) that will be the cell's content by setting this weak reference when OnContextChanged is called and unsetting it when OnPropertyChanging for the BindableObject.BindingContextProperty property is called. This means, when the cell is selected, ListView gives me the selected item which in turn I use to look up the cell's content's VisualElement. Once I have that VisualElement, I can set it as the target for BubblePopup.

    Sort of off topic, if you don't set BubblePopup's hostPage attribute, it will do exactly what you're doing in your code: set the hostPage to Application.Current.MainPage.

    Again, I may have completely misunderstood what you are passing to BubblePopup's target parameter. If that's the case, take a moment to give me a better picture of what kind of element you're using as the target.

    Ben

  • MichaelSattlerMichaelSattler Michael Sattler USMember

    @BuildCalc

    Thanks for your response!

    My intention was to use "Vertical" as the pointer direction, as usually any cell in a list would always have some space above or below it. I also tried setting the pointer direction to "None" and not specifying a target view, but that didn't help either. I even tried using the ModalPopup instead, but that also didn't work (it did on the "rather simple page" I mentioned, however).

    Can you tell me a bit about what Page or View the popups attach to? I have a strong suspicion that one of the numerous custom renderers in my project is killing the popups (our customers want practically any view and layout customized in some way - don't ask why we went with XForms in the first place).

  • BuildCalcBuildCalc Ben Askren USMember ✭✭✭

    @MichaelSattler

    Can you tell me a bit about what Page or View the popups attach to?

    The popups attach themselves to all of the stock Xamarin Forms pages (CarouselPage, MasterDetailPage, NavigationPage, Page, and TabbedPage). I just realized that TemplatedPage is missing (I'll fix that in the next release). They attach at the native renderer level, not the PCL element level.

    I have a strong suspicion that one of the numerous custom renderers in my project is killing the popups ...

    Since ModalPopup doesn't work as well, this appears to be a likely hypothesis. Those renderers could be disconnecting the connection of the Forms9Patch popups to the page renderers.

    If your hypothesis is correct, additions to Forms 2.1 may give me a path to address this. Before investing a great deal of time into this, it would be good to know that this is the root cause. Rather than continuing to go back and forth, would you be willing to do a screen share session so I can get a much better feel for your application?

  • MichaelSattlerMichaelSattler Michael Sattler USMember

    @BuildCalc

    Yup, seems my hypothesis was indeed correct. I just tried disabling the custom renderer for my app's main page, and - tadah! - the ModalPopup/BubblePopup appeared.

    Unfortunately, for me this means I can't use your lib, because I can't get rid of our own custom renderers. Which is a real bummer, because I really like your lib's API and the options it provides. But I'll definitely keep it in mind for future projects!

    Side note: Attaching the BubblePopup to a ViewCell was actually pretty easy. I just needed to pass the ViewCell's View property to the BubblePopup as the target to display the popup next to the cell.

  • BuildCalcBuildCalc Ben Askren USMember ✭✭✭

    @MichaelSattler

    Thanks for confirming your hypothesis. I'm going to work on changing how the popups bind to the renderers and will let you know when the update is available. I would greatly appreciate it if you could test it then.

  • MichaelSattlerMichaelSattler Michael Sattler USMember

    I hope I'll find some time to test the update when it's ready. Thanks for your great support!

  • BuildCalcBuildCalc Ben Askren USMember ✭✭✭

    @MichaelSattler

    Looks like the approach I had in mind won't work at this time. If at sometime in the future it does, I'll update the package.

  • BuildCalcBuildCalc Ben Askren USMember ✭✭✭
    edited April 2016

    @MichaelSattler

    I have a suggestion for you that might work. In your custom page renderers, give this a try (the following is for your version of NavigationRenderer, you would alter appropriately for your other renderers):

    using Forms9Patch.iOS;
    
    namespace YourCustomRendererNameSpace.iOS
    {
        public class YourCustomNavigationRenderer : Xamarin.Forms.Platform.iOS.NavigationRenderer
        {
            protected override void OnElementChanged (VisualElementChangedEventArgs e)
            {
                base.OnElementChanged (e);
                this.PageRendererExtensionOnElementChanged (e);
            }
    
            protected override void Dispose (bool disposing)
            {
                this.PageRendererExtensionDispose (disposing);
                base.Dispose (disposing);
            }
        }
    }
    
    
    
  • MichaelSattlerMichaelSattler Michael Sattler USMember

    @BuildCalc

    Which class or extension do the PageRendererExtensionOnElementChanged and PageRendererExtensionDispose methods belong to?

    My application's root page is a TabbedPage, so I'll have to change the TabbedRenderer for this...

  • BuildCalcBuildCalc Ben Askren USMember ✭✭✭

    Those two methods are both in Forms9Patch.iOS and Forms9Patch.Droid. You would use them in the iOS and Android versions of your custom TabbedRenderer - which means you will have to:

    • add a reference to Forms9Patch.iOS in your TabbedRenderer's iOS project
    • add a reference to Forms9Patch.Droid in your TabbedRenderer's Droid project
    • add a using Forms9Patch.iOS; statement to the beginning of your TabbedRenderer's iOS file
    • add a using Forms9Patch.Droid; statement to the beginning of your TabbedRenderer's Android file
  • BuildCalcBuildCalc Ben Askren USMember ✭✭✭
    edited April 2016

    iOS:

    using Xamarin.Forms;
    using Xamarin.Forms.Platform.iOS;
    using Forms9Patch.iOS;
    
    namespace ***YOUR NAME SPACE.iOS***
    {
        public class TabbedRenderer : Xamarin.Forms.Platform.iOS.TabbedRenderer
        {
            protected override void OnElementChanged (VisualElementChangedEventArgs e)
            {
                base.OnElementChanged (e);
                this.PageRendererExtensionOnElementChanged (e);
            }
    
            protected override void Dispose (bool disposing)
            {
                this.PageRendererExtensionDispose (disposing);
                base.Dispose (disposing);
            }
        }
    }
    
    

    Android:

    using Xamarin.Forms;
    using Xamarin.Forms.Platform.Android;
    using Forms9Patch.Droid
    
    namespace ***YOUR NAME SPACE***.Droid
    {
        internal class TabbedRenderer : Xamarin.Forms.Platform.Android.TabbedRenderer
        {
            protected override void OnElementChanged (ElementChangedEventArgs<TabbedPage> e)
            {
                base.OnElementChanged (e);
                this.PageRendererExtensionOnElementChanged (new VisualElementChangedEventArgs(e.OldElement, e.NewElement));
            }
    
            protected override void Dispose (bool disposing)
            {
                this.PageRendererExtensionDispose (disposing);
                base.Dispose (disposing);
            }
        }
    }
    
    
  • Maharshi.5212Maharshi.5212 Maharshi USMember ✭✭

    @BuildCalc
    Will you guys support winrt and windows phone also?

  • TobiasSchulz.9796TobiasSchulz.9796 Tobias Schulz DEMember ✭✭

    @BuildCalc

    Maybe you could use an Effect attached to the Page's? You could call these two methods in the Effect instead of the renderer?

  • BuildCalcBuildCalc Ben Askren USMember ✭✭✭
    edited April 2016

    @TobiasSchulz.9796

    Funny you would suggest that! See this Xamarin Forms discussion. The net is I had started to but something was not behaving in my Xamarin environment. Whatever it was, I had my fooled in believing PlatformEffects don't work on Page elements. Now that I'm passed that, I will start refactoring.

  • BuildCalcBuildCalc Ben Askren USMember ✭✭✭

    @Maharshi.5212

    Yes, I will be working on it later this spring.

  • JSparrowJSparrow Bikash USMember

    I have downloaded the code and bubble works fine, but when I copied the ChatListPage in my cs file (I copied the complete file and pasted it message.cs and Replaced the class name and constructor to Message and also created one page ImageCircle),

    But now thing is "DataTemplateSelector" throwing error.

    Can you help me on this why its throwing error when I included it in my file.

  • BuildCalcBuildCalc Ben Askren USMember ✭✭✭

    @Bikash00789

    Perhaps I could if I had much more information. A sample project via Github would be best. Sample code posted in a response here might still be helpful.

  • BuildCalcBuildCalc Ben Askren USMember ✭✭✭

    @TobiasSchulz.9796 -

    It appears that PlatformEffects don't work on Page elements on iOS (but do on Android). You can see this sample project to see what I mean.

«13
Sign In or Register to comment.