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.
Simply stated, Forms9Patch is two separate elements (Image and ImageSource) which are multi-screen / multi-resolution extensions of their Xamarin Forms counterparts.
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 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.
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, }
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>
Project page: http://Forms9Patch.com
Nuget page: https://www.nuget.org/packages/Forms9Patch/0.9.1
Demo app repository: https://github.com/baskren/Forms9PatchDemo
Posts
Version 0.9.5 (https://www.nuget.org/packages/Forms9Patch) is now available. This release adds the following functionality:
Version 0.9.7 is now available with some great enhancements:
Forms9Patch.Image
now has tinting via theTintColor
property.ContentView
,Frame
,AbsoluteLayout
,Grid
,RelativeLayout
, andStackLayout
support using aForms9Patch.Image
as a background - which means it can be NinePatch scaled, aspect filled, aspect fitted, stretched to fit, or tiled.Frame
,AbsoluteLayout
,Grid
,RelativeLayout
, andStackLayout
support a background outline with color, width and radius properties. If theBackgroundColor
property is set, theHasShadow
works (on Android, too) and theShadowInverse
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:Also, I should have noted a long time ago that the
Forms9Patch.ImageSource
, and the non-image portions ofFrame
,AbsoluteLayout
,Grid
,RelativeLayout
,StackLayout
, andMaterialButton
are free to use without a license.You can learn more and see examples at http://Forms9Patch.com
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:Take a look at http://Forms9Patch.com for documentation and examples.
Version 0.9.9.3 is now available. All button response times and
Forms9Patch.ImageSource.FromMultiResource
andForms9Patch.ImageSource.FromResource
image loading times are as good (and some cases better) than their Xamarin.Forms counterparts.ImageButton
now has thePressingState
- 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 theLongPressing
andLongPressed
events.Version 0.9.10 is now available and it has a very special addition - Modal and Bubble popups!
These automatically place the popup over the top most
Xamarin.Forms.Page
(unless you chose another page). Choosing the target of aForms9Patch.BubblePopup
is just of matter of setting theTarget
property to aXamarin.Forms.View
.There's a lot more information about them at http://Forms9Patch.com
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.
@AlanSpires
Yes, it should work on Droid just as well (it is used in the demo app as such).
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?
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.
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!
@Matthew.4307
Thanks for letting me know. I'll be working on that later this spring.
@AlanSpires -
In your example, are you using a ListView or a StackLayout in a ScrollView?
Listview with item template selector for left and right cells
@AlanSpires , can you send me a copy of the template?
The template selector is nothing special. To send you the cells would be a lil tricky with the dependencies and what not.
I emailed you the cell, should be able to strip out the dependencies without a issue
Got it.
@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.
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!
Here is the issue I'm running into
and below is the code that I'm using in a simple XF Portable Project
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
@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
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:
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:
The page I'm passing is the ListView's ContentPage.
@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
@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).
@MichaelSattler
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.
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?
@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.
@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.
I hope I'll find some time to test the update when it's ready. Thanks for your great support!
@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.
@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):
@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...
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:
using Forms9Patch.iOS;
statement to the beginning of your TabbedRenderer's iOS fileusing Forms9Patch.Droid;
statement to the beginning of your TabbedRenderer's Android fileiOS:
Android:
@BuildCalc
Will you guys support winrt and windows phone also?
@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?
@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.
@Maharshi.5212
Yes, I will be working on it later this spring.
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.
@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.
@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.