Custom Renderer for ContentView (iOS) - ios

I have a delared the following
public class ViewBase : ContentView
{
//...
}
When I use that in XAML
<local:ViewBase xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:MyForms;"
x:Class="MyForms.Finder"
BackgroundColor="Color.Yellow">
<!-- ... -->
</local:ViewBase>
When I use a CustomRenderer for that and even (as below) do nothing in it, the BackgroundColor from above is not set. When I don't define the following lines the background is yellow as expected.
[assembly: ExportRenderer(typeof(ViewBase), typeof(ViewRendererBase))]
namespace MyiOS
{
public class ViewRendererBase : ViewRenderer
{
}
}
BackgroundColor is a Property of ViewRenderer. I had a look into the code, it seems as Control is no set (I don't call SetNativeControl) it can't set Control.BackgroundColor to a value. But why does this happen? My guess is that something is wrong with the inheritance of ViewRenderer, as the default behaviour uses something different on ContentView!?

Not sure whether this is a bug in our docs [1] or a bug in the iOS ViewRenderer code [2] for the SetBackgroundColor method. So there are a couple of ways to workaround this. One is to have your custom renderer inherit from VisualElementRenderer<T> instead, e.g.:
public class ViewBaseRenderer : VisualElementRenderer<ContentView>
{
//...
}
When checking on the default renderer type in iOS code with:
var contentRenderer = Platform.CreateRenderer(new ContentView())
var rendererType = contentRenderer.GetType();
rendererType is a VisualElementRenderer<T>, so this would seem to be the default renderer that is used by Forms, so it would seem to be a bug in the docs.
Another "workaround" would be to use the ViewRenderer but override the SetBackgroundColor method:
public class ViewBaseRenderer : ViewRenderer
{
protected override void SetBackgroundColor(Color color)
{
base.SetBackgroundColor(color);
if (NativeView == null)
return;
if (color != Color.Default)
NativeView.BackgroundColor = color.ToUIColor();
}
I have brought this up with the Xamarin Forms team to determine whether it is a bug in the docs or a bug in the Forms iOS Platform code for the ViewRenderer. If you look at the forms source code I linked [2] you will see that if Control is null, which it is in this case, the background color is never set. By overriding and adding the code to set the background color for NativeView then you can workaround this possible bug.
So apparently it seems the docs are in error. If you use ViewRenderer you have to set the Control yourself. Other renderers that inherit from ViewRenderer, like LabelRender, set the Control already, but ViewRenderer does not, so you would have to call SetNativeControl() in the OnElementChanged override to create and set a native control. See this forum post [3]. Personally, I think that one should inherit from the renderer that is used by default, which in this case is a VisualElementRenderer<T>
[1] https://developer.xamarin.com/guides/xamarin-forms/custom-renderer/renderers/#Layouts
[2] https://github.com/xamarin/Xamarin.Forms/blob/74cb5c4a97dcb123eb471f6b1dffa1267d0305aa/Xamarin.Forms.Platform.iOS/ViewRenderer.cs#L99
[3] https://forums.xamarin.com/discussion/comment/180839#Comment_180839

Related

ComposeView with view binding

I added a ComposeView in my XML layout file. I use view binding to inflate this file in my Activity. When I try to call binding.myComposeView.setContent { ... } then I get the following compilation error: Unresolved reference: setContent. When I take a look at the generated binding file, the type of myComposeView is View and not ComposeView. When I use findViewById<ComposeView>(R.id.myComposeView).setContent { ... } then everything works fine. Why is the binding not generated correctly? What can I do to use view binding with a ComposeView?
It turns out that I had two versions of the same layout: portrait and horizontal. I converted the portrait one to Compose by replacing a LinearLayout with a ComposeView. However, in the horizontal layout myComposeView was still a LinearLayout. That's why the view binding class that got created had a field myComposeView of type View instead of ComposeView. The view with the same id had different types in two layout versions.
Maybe there is a problem with the way the binding is set up in onCreate of your activity. Are you using something along the lines of the following code? :
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
binding.composeView.setContent {
MaterialTheme {
Text(text = "Hello World")
}
}
}

Vaadin checkbox is not correctly rendered

Using Vaadin 14.0.13 without compatibility mode.
I use a view to create a Dialog with dynamic content:
#Route("")
public class MainView extends VerticalLayout {
public MainView(DialogContentProvider contentProvider) {
this.add(new Button("Click me!", event -> new Dialog(contentProvider.create()).open()));
}
}
The contentProvider is an interface
public interface DialogContentProvider {
Component create();
}
with this implementation:
public class CheckBoxContentProvider implements DialogContentProvider {
#Override
public Component create() {
return new Checkbox("My checkbox", true);
}
}
instantiated by Spring Boot (version 2.2.1.RELEASE) with a bean:
#Bean
public DialogContentProvider contentProvier() {
return new CheckBoxContentProvider();
}
When I click on the button, the dialog is opened but the checkbox haven't the box:
The source code is on github: https://github.com/gronono/bug-vaadin-checkbox
I don't understand why and how I can fix it. If I include the checkbox creation inside the main view, it works fine:
#Route("")
public class MainView extends VerticalLayout {
public MainView(DialogContentProvider contentProvider) {
// this.add(new Button("Click me!", event -> new Dialog(contentProvider.create()).open()));
this.add(new Button("Click me!", event -> new Dialog(new Checkbox("My checkbox", true)).open()));
}
}
This sound an awful lot like this (related github issue)
Basically, this happens when you don't have any View that uses a Checkbox directly, but through other means like reflection or in your case the contentProvider, because in no view of your app there is any import statement of Checkbox (--> therefore, vaadins scan during the installation will not detect usages of Checkbox, so it will not download npm stuff for checkbox).
in the github it says this will be fixed in 14.1
If you need a fix now, for me it worked when I declared a field of that type in any view with a #Route. That field doesn't have to be used.
#Route("")
public class MainView extends VerticalLayout {
private Checkbox unusedCheckbox; // this line fixes it.
public MainView(DialogContentProvider contentProvider) {
this.add(new Button("Click me!", event -> new Dialog(contentProvider.create()).open()));
}
}
Addendum: This is not related to the Checkbox component specifically, it happens with any vaadin component that isn't initially scanned in a route, but used anyway through reflective-, provider-, or generic means.
Edit: You can also work around this currently by adding a #Route(registerAtStartup = false) to your provider that uses the Checkbox directly. This will make vaadins scan see the checkbox usage (therefore importing its npm package), but will not actually register the provider as a real route..
Another way which I prefer if you need this for multiple components is to create a new View with a #Route(registerAtStartup = false) which only defines private variables for each component that you'll need in the application (and arent already used directly in some view of yours). This has the advantage of all these component usage definitions in one place, and once the official fix is released, you need only to delete one class and the deprecated workaround is gone.
#Route(registerAtStartup = false)
public class ComponentImportView extends VerticalLayout {
private Checkbox checkBox;
private Upload upload;
private ProgressBar progressBar;
}

Is possible to override Xamarin Forms control properties from native custom renderer?

I have implemented native iOS custom renderer for Entry Xamarin Forms control where changed some properties like BackgroundColor.
I need to override some properties from custom renderer. Is this possible?
Your question is hard to understand (what you exactly want to do).
In my XF-app, I use an Editor-Control on a page.
As the default-fontsize on iOS is to small, I have implemented a custom-render to set a larger font (especially and only) for iOS.
You also can override other properties (similar to he fontsize in the example).
In XF add:
public class MG_Editor : Editor // Interface to specific Renderer
{
// only placeholder for interface
}
In the iOS-project add a class like "iOS_Specific.cs":
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
//
// additionall usings
//
using MatrixGuide; // your namespace
using MatrixGuide.iOS; // your namespace.iOS
//
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
//
using Foundation;
using UIKit;
[assembly: ExportRenderer(typeof(MG_Editor), typeof(EditorCustomRenderer))]
namespace MatrixGuide.iOS
{
public class EditorCustomRenderer : EditorRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Editor> e)
{
base.OnElementChanged(e);
if (e.OldElement == null)
{ // perform initial setup
// lets get a reference to the native control
var nativeTextView = (UITextView)Control;
// do whatever you want to the UITextField here!
nativeTextView.Font = UIFont.SystemFontOfSize(18);
}
}
}
}
Then in the XF-Code (E.G. on a page) create the control:
var EditorxxYourWishedNamexx = new MG_Editor();
So, for Android and WP the standard Editor-control is used, whereby for iOS the custom-implementation (with the larger font) is used.
Notes:
- MatrixGuide is the namespace of my app.
- Instead of EditorxxYourWishedNamexx you can take a name you like.
- Similar you also can implement custom renderers for the other platforms.

Meaning of exclamation mark in iOS Designer on Xamarin Studio

I'm working with the iOS Designer and I have a storyboard with different views on it (navigation controller, ...). On nearly each view there is a red exclamation mark on the right side at the bottom. I get errors like
System.NullReferenceException
Object reference not set to an instance of an object
If I go to the corresponding code line:
public override UIInterfaceOrientationMask GetSupportedInterfaceOrientations ()
{
return TopViewController.GetSupportedInterfaceOrientations();
}
This is part of my CustomNavigationController. If I set the class name on the iOS Designer I get the error. What do I have to add here? The constructor looks like the following:
public CustomNavigationController (IntPtr handle) : base (handle)
{
}
Do I have to instantiate it in the AppDelegate?
On another view controller I get a similar Exception (with a different stack) and it points me to this line:
this.NavigationController.NavigationBar.TintColor = UIColor.FromRGB (21, 66, 139);
The app seems to work fine on simulator and on device though.
How do I resolve this issue?
EDIT:
I now removed the code in my view controller:
this.NavigationController.NavigationBar.TintColor = UIColor.FromRGB (21, 66, 139);
As replacement I moved this code to the Application Delegate FinishedLaunching():
UINavigationBar.Appearance.TintColor = UIColor.FromRGB (21, 66, 139);
The result is that now I don't have the exclamation mark for this view controller and the color is as desired. But the preview in the iOS Designer doesn't show me the correct color anymore.
If I comment out GetSupportedInterfaceOrientations() in my CustomNavigationController than the other exclamation mark has been removed. I thought I need this function for defining the orientation of each view controller. A quick look showed that it seems to work.
There are restriction on what the design time can support. You should replace any dynamic data, for example data loaded from a service, with dummy data when on design mode. The same applies to runtime objects like TopViewController which is only available at runtime.
if (Site != null && Site.DesignMode)
{
// use dummy data for designer surface
}
else
{
// use live data
}
This document explains it in more detail:
http://developer.xamarin.com/guides/ios/user_interface/designer/ios_designable_controls_overview/

Cannot change the title of the cancel button in the UISearchBar

I would like to change the title of the cancel button of the UISearchBar using MonoTouch.
Yes I have seen this "hack" but I prefer a more elegant way. I am trying to use:
UIButton.AppearanceWhenContainedIn(typeof(UISearchBar)).SetTitle("xxxx");
which it is supposed to work in objective-c but in MonoTouch I get the following error
message: Error CS1061: Type
MonoTouch.UIKit.UIButton.UIButtonAppearance' does not contain a
definition forSetTitle' and no extension method SetTitle' of type
MonoTouch.UIKit.UIButton.UIButtonAppearance' could be found (are you
missing a using directive or an assembly reference?)
I am using Xamarin Studio ver. 4.0
It's hackish - the Objective-C code works because the current implementation of UIAppearance use a proxy object. IOW Apple did not promise to keep the implementation identical and that specific feature is not documented.
Now that you're aware of this and if you still want to use it with Xamarin.iOS then you can do it using the hack I described in this Q&A. That will allow you to use any UIButton API on the UIAppearance proxy.
Why not use a bit more lightweight hack?
public static void SetSearchBarCancelButtonTitle (UISearchBar sb, string title)
{
foreach (var subview in sb.Subviews)
if (subview is UIButton)
(subview as UIButton).SetTitle (title, UIControlState.Normal);
}
UIButton no longer appears to be a direct subView of UISearchBar. You'll have to recurse like the following, until it is found:
public static bool SetButton(UIView parentView, string title, UIColor tint)
{
foreach (var sub in parentView.Subviews)
{
if (sub is UIButton)
{
(sub as UIButton).SetTitle(title, UIControlState.Normal);
(sub as UIButton).SetTitleColor(tint, UIControlState.Normal);
return true;
}
if (SetButton(sub, title, tint))
return true;
}
return false;
}

Resources