I’m trying to create a “generic” renderer for iOS and I have trouble figuring out the dimensions of the renderer.
The class of the renderer in the PCL is very simple and the only “peculiar” is that inherits from a View since I want to have it as a generic renderer
public class ExampleView : View { }
In the XAML, it is also very typical
<renderers:ExampleView />
The definition of the renderer in the iOS project is the following
public class ExampleRenderer : ViewRenderer<ExampleView, UIView>
{
protected override void OnElementChanged(ElementChangedEventArgs<ExampleView> e)
{
base.OnElementChanged(e);
if (Control == null)
{
SetNativeControl(new UIView(new CGRect(0, 0, 0, 300)));
}
}
}
The interesting part here is that the class inherits from ViewRenderer<ExampleView, UIView>
It seems that although I specify that the width of the render to be 0, the actual renderer takes the entire width of the page.
In fact whatever value I put as width, will be disregarded, and the renderer will occupy the entire page.
This is not bad, it is unintentional and this is what bother me. (or Am I doing something wrong?)
So the question here is how can find in my custom render, the width that occupies?
It is not the renderer, but the native control that is taking up the space. The renderer is doing it's job i.e. keep the native control in sync with forms control - which includes layout parse requests.
To control the size constraints I would recommend using the properties WidthRequest or HeightRequest on forms Element to do that. It will automatically trickle down to the native Control.Frame through the renderer.
EDIT-1
If you want to update your native controls (or subviews) based on current bounds you can use OnElementPropertyChanged to track the changes. There can many reasons that can cause a size-update - like device-orientation change, parent layout, first layout pass etc.
public class ExampleRenderer : ViewRenderer<ExampleView, UIView>
{
protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == VisualElement.WidthProperty.PropertyName ||
e.PropertyName == VisualElement.HeightProperty.PropertyName)
{
if(Element != null && Element.Bounds.Height > 0 && Element.Bounds.Width > 0)
{
//update size on native control's subviews
// or use SetNeedsDisplay() to re-draw
//Note: force update to Control's size is not recommended (as it is recommended it be same as forms element)
//Instead add a subview that you can update for size here
}
}
}
Related
Is there an Image Button Renderer in Xamarin.MacOS?
I am using Xamarin.Forms and all my image buttons in the common code are not shown at all in Xamarin.MacOS. Is Image Button supported in Xamarin.MacOS?
Is there an Image Button Renderer in Xamarin.MacOS?
Unfortunately, after checking the source code of Xamarin.Forms.Platform.macOS, there is no ImageButtonRenderer there now. I guess the reason that there is no control is the similar with ImageButton. I'm not sure whehter designer will add this in the future. Whatever, now it not exists.
Is Image Button supported in Xamarin.MacOS?
From the above said, MacOS has no control like ImageButton of Forms. Therefore, if used ImageButton in Xamarin Froms, it will not show in Xamarin.MacOS.
However, there is a workaround. You could use Button in Forms, and add Image in ButtonRenderer in Xamarin.MacOS. Because NSButton has a Image property.
For example, the code of ButtonRenderer as follows:
[assembly: ExportRenderer (typeof(MyButton), typeof(MyButtonRenderer))]
namespace CustomRenderer.MacOS
{
public class MyButtonRenderer : ButtonRenderer
{
protected override void OnElementChanged (ElementChangedEventArgs<Button> e)
{
base.OnElementChanged (e);
if (Control != null) {
// do whatever you want to the UITextField here!
Control.Image= NSImage.ImageNamed("tree")
Control.ImageScaling = NSImageScale.AxesIndependently;
}
}
}
}
The image source (tree.png) is the same way with iOS to be added in Resource folder, and set Propertied : Build Action -> BundleResource.
I noticed that after using the .HeightRequest property on a View in Xamarin.Forms that a method I have assigned to the SizeChanged event is no longer called.
The content within the View that had called .HeightRequest is a Label that contains text which can be zoomed in. When zooming in, the text now exceeds the "bounds" of the View's height, and the height is no longer adjusting as it once was.
public ExpandableEntryView(...)
{
InitializeComponent();
// my beautiful code
SizeChanged += ExpandableEntryView_SizeChanged;
UpdateUI();
}
private void ExpandableEntryView_SizeChanged(object sender, EventArgs e)
{
// this is never called once .HeightRequest is assigned a value
}
private void SomeFunction() {
sectionsStackLayout.HeightRequest = 0; // this causes SizeChanged to no longer work
}
I essentially need this View to minimize its height to 0, and then return to its original height when I tell it to. I am also using IsVisible, but the Height property is required for an animation I am trying to do.
Thank you in advance.
Nevermind, I was able to figure it out. The reason why the SizeChanged event is no longer called is because the view is no longer responsible for keeping track of its size after assigning a value to HeightRequest. So, if you would like for your View to return to being able to determine its own size once again, (i.e. if a child element in that View happens to change size/scale) you need to assign the HeightRequest to -1. This will then reenable the SizeChanged event.
Hope this helps somebody!
I'm trying to apply a background for what ever the control the effect is attached to.
First i made the effect to apply a background for the controls that have either a Fill or Background properties in UWP.
But the problem is a lot of controls renderes to FrameworkElement in UWP. And FrameworkElement doesn't have a Background property .
In the VisualElementRenderer Xamarin forms adresses this issue be applying a background layer behind the content but this is an effect.
Is there is any way to apply a background for whatever the effect is attached to?
I'm appplying an AcrylicBrush So it must be assigned to the Control directly.
The code i written before:
try
{
Control.GetType().GetProperty("Background").SetValue(Control, brush);
}
catch
{
Control.GetType().GetProperty("Fill").SetValue(Control, brush);
}
I'm appplying an AcrylicBrush So it must be assigned to the Control directly.
You could use compositor to apply Acrylic to SpriteVisual. We use Win2D to make GaussianBlurEffect for UIElement before we have Acrylic API. Get Compositor of UIElement, and use this Compositor to CreateSpriteVisual. then set GaussianBlurEffect to hostSprite.Brush like the following.
Compositor _compositor;
SpriteVisual _hostSprite;
private void applyAcrylicAccent(Panel panel)
{
_compositor = ElementCompositionPreview.GetElementVisual(panel).Compositor;
_hostSprite = _compositor.CreateSpriteVisual();
_hostSprite.Size = new Vector2((float)panel.ActualWidth, (float)panel.ActualHeight);
var backdrop = _compositor.CreateHostBackdropBrush();
// Use a Win2D blur affect applied to a CompositionBackdropBrush.
var graphicsEffect = new GaussianBlurEffect
{
Name = "Blur",
BlurAmount = 100f,
Source = new CompositionEffectSourceParameter("backdrop")
};
var effectFactory = Window.Current.Compositor.CreateEffectFactory(graphicsEffect, new[] { "Blur.BlurAmount" });
var effectBrush = effectFactory.CreateBrush();
effectBrush.SetSourceParameter("backdrop", backdrop);
_hostSprite.Brush = effectBrush;
ElementCompositionPreview.SetElementChildVisual(panel, _hostSprite);
}
And calling it with applyAcrylicAccent(RootLayout). You also will need to handle the SizeChanged event :
private void LayoutRoot_SizeChanged(object sender, SizeChangedEventArgs e)
{
if (_hostSprite != null)
_hostSprite.Size = e.NewSize.ToVector2();
}
I have implemented a custom clickable label class in Xamarin.Forms along with a custom renderer, that adds a RippleDrawable as the controls Foreground. I am creating the RippleDrawable with the following code:
public static Drawable CreateRippleDrawable(Context context)
{
var typedValue = new TypedValue();
context.Theme.ResolveAttribute(Resource.Attribute.SelectableItemBackground, typedValue, true);
var rippleDrawable = context.Resources.GetDrawable(typedValue.ResourceId, context.Theme);
return rippleDrawable;
}
In my custom renderer I assign the drawable
this.Control.Foreground = DrawableHelper.CreateRippleDrawable(this.Context);
and update the ripple when the user touches the control
private void LinkLabelRenderer_Touch(object sender, TouchEventArgs e)
{
if (e.Event.Action == MotionEventActions.Down)
{
this.Pressed = true;
}
if (e.Event.Action == MotionEventActions.Cancel)
{
this.Pressed = false;
}
if (e.Event.Action == MotionEventActions.Up)
{
this.Ripple.SetHotspot(e.Event.GetX(), e.Event.GetY());
this.Pressed = false;
// raise the event of the Xamarin.Forms control
}
}
Now, whenever I click the control, the ripple will be shown, which is the expected behavior, but if I touch (tap or long-press) the parents of the control (e.g. the StackLayout, Grid or whatever layout contains the label, including their parent Layout, Page or View) the ripple animation will be triggered. Anyway, the event handler LinkLabelRenderer_Touch in not called in this case, only when the actual control is touched.
I can work around this behavior by adding an empty GestureRecognizer to the respective parent(s), but I really dislike this solution, because this is but a hack. And to make things worse it is a hack I'll always have to remember whenever I use the control.
How can I prevent the RippleDrawable being shown when the parent is touched?
Turned out I got things fundamentally wrong. Subscribing the Touch event is not the way to go. I had to make the control clickable and subscribe the Click event
this.Control.Clickable = true;
this.Click += LinkLabelRenderer_OnClick;
There is no need to handle all that RippleTouch stuff the way I did (via the Touch event) but could let android handle things for me.
When I try to write a custom renderer for any of the layout classes, the "Control" property appears null. It is OK since the renderer already is the wrapper for the native control but it is a problem when creating an Effect. So I cannot do any customization with the layout classes using Effects since Control property is not assigned. Is this a bug?
The PlatformEffect class has another property called Container which could also be used for the effect when the Control is null, e.g. at layouts.
So I saw the same issue as the original post.
I was inheriting PlatformEffect<UIView, UILabel> on iOS. The control was null when I was debugging. I changed my Effect to inherit from PlatformEffect and then cast the control as ((UILabel)Control) and everything started working.
Returned a Null Control
MultilineTruncateLabelEffect : PlatformEffect<UIView, UILabel>
{
protected override void OnAttached()
{
var effect = (MultilineEffect) Element.Effects.FirstOrDefault(e => e is MultilineEffect);
if (effect != null)
{
Control.Lines = effect.Lines;
}
}
}
Worked Fine
public class MultilineTruncateLabelEffect : PlatformEffect
{
protected override void OnAttached()
{
var effect = (MultilineEffect) Element.Effects.FirstOrDefault(e => e is MultilineEffect);
if (effect != null)
{
((UILabel)Control).Lines = effect.Lines;
}
}
}
Layout do not have renderers, and as they have no renderers or native views (i.e. no Control), there's nothing you could possibly modify with an Effect.