This issue only occurs with my implementation of native ads. Banner ads work fine. I adapted the custom renderers from this blog to work with native ads. I used test ad unit IDs.
Here is my custom renderer for Android:
[assembly: ExportRenderer(typeof(NativeAdView), typeof(NativeRendererAndroid))]
namespace XXX.Droid
{
public class NativeRendererAndroid : ViewRenderer<NativeAdView, AdView>
{
public NativeRendererAndroid(Context context) : base(context) { }
protected override void OnElementChanged(ElementChangedEventArgs<NativeAdView> e)
{
base.OnElementChanged(e);
if (e.NewElement != null && Control == null)
SetNativeControl(CreateAdView());
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == nameof(AdView.AdUnitId))
Control.AdUnitId = Element.AdUnitId;
}
private AdView CreateAdView()
{
var adView = new AdView(Context)
{
AdSize = new AdSize(AdSize.FullWidth, AdSize.AutoHeight),
AdUnitId = Element.AdUnitId
};
adView.LayoutParameters = new LinearLayout.LayoutParams(LayoutParams.MatchParent, LayoutParams.MatchParent);
adView.LoadAd(new AdRequest.Builder().Build());
return adView;
}
}
}
And for iOS:
[assembly: ExportRenderer(typeof(NativeAdView), typeof(NativeRendereriOS))]
namespace XXX.iOS
{
public class NativeRendereriOS : ViewRenderer<NativeAdView, NativeExpressAdView>
{
protected override void OnElementChanged(ElementChangedEventArgs<NativeAdView> e)
{
base.OnElementChanged(e);
if (Control == null)
{
SetNativeControl(CreateNativeAd());
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == nameof(NativeExpressAdView.AdUnitID))
Control.AdUnitID = Element.AdUnitId;
}
private NativeExpressAdView CreateNativeAd()
{
AdSize adSize = new AdSize();
adSize.Size = new CGSize(UIScreen.MainScreen.Bounds.Size.Width, 49);
var nativeAd = new NativeExpressAdView(adSize)
{
AdUnitID = Element.AdUnitId,
RootViewController = GetVisibleViewController()
};
nativeAd.LoadRequest(GetRequest());
Request GetRequest()
{
var request = Request.GetDefaultRequest();
return request;
}
return nativeAd;
}
private UIViewController GetVisibleViewController()
{
var windows = UIApplication.SharedApplication.Windows;
foreach (var window in windows)
{
if (window.RootViewController != null)
{
return window.RootViewController;
}
}
return null;
}
}
}
These custom renderers are very similar to the ones linked in the blog above. Perhaps there is something I need to include for native ads that is not used for banner ads?
Once I get the ad loading correctly, I would like to put it in a ListView, but for now I just have it as a View.
Edit: For clarification, when using the above custom renderers, everything builds successfully, but when the ad tries to load, 'Ad failed to load : 0' gets printed in the debug and the NativeAdView remains transparent. This is both both platforms.
Related
Hi i implemented the xamarin video player which is described the following link https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/custom-renderer/video-player/
i am downloading videos through code to the documents folder of the application which has a path like this
[/Users/vaibhavjain/Library/Developer/CoreSimulator/Devices/{GUID}/data/Containers/Data/Application/{GUID}/Documents/MediaDocuments/Nature1.mp4]
now i checked that videos are downloaded correctly to this path but when i am passing this path as source to the video player above it is not able to play it
my guess is that the player is not able to reach the path or it is expecting a relative path but i am unable to find any example of the type of path i should provide.
here is the code for custom Renderer on ios
public class VideoPlayerRenderer : ViewRenderer<VideoPlayer, UIView>
{
AVPlayer player;
AVPlayerItem playerItem;
AVPlayerViewController _playerViewController; // solely for ViewController property
public override UIViewController ViewController => _playerViewController;
protected override void OnElementChanged(ElementChangedEventArgs<VideoPlayer> args)
{
base.OnElementChanged(args);
if (args.NewElement != null)
{
if (Control == null)
{
// Create AVPlayerViewController
_playerViewController = new AVPlayerViewController();
// Set Player property to AVPlayer
player = new AVPlayer();
_playerViewController.Player = player;
var x = _playerViewController.View;
// Use the View from the controller as the native control
SetNativeControl(_playerViewController.View);
}
SetAreTransportControlsEnabled();
SetSource();
args.NewElement.UpdateStatus += OnUpdateStatus;
args.NewElement.PlayRequested += OnPlayRequested;
args.NewElement.PauseRequested += OnPauseRequested;
args.NewElement.StopRequested += OnStopRequested;
}
if (args.OldElement != null)
{
args.OldElement.UpdateStatus -= OnUpdateStatus;
args.OldElement.PlayRequested -= OnPlayRequested;
args.OldElement.PauseRequested -= OnPauseRequested;
args.OldElement.StopRequested -= OnStopRequested;
}
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (player != null)
{
player.ReplaceCurrentItemWithPlayerItem(null);
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs args)
{
base.OnElementPropertyChanged(sender, args);
if (args.PropertyName == VideoPlayer.AreTransportControlsEnabledProperty.PropertyName)
{
SetAreTransportControlsEnabled();
}
else if (args.PropertyName == VideoPlayer.SourceProperty.PropertyName)
{
SetSource();
}
else if (args.PropertyName == VideoPlayer.PositionProperty.PropertyName)
{
TimeSpan controlPosition = ConvertTime(player.CurrentTime);
if (Math.Abs((controlPosition - Element.Position).TotalSeconds) > 1)
{
player.Seek(CMTime.FromSeconds(Element.Position.TotalSeconds, 1));
}
}
}
void SetAreTransportControlsEnabled()
{
((AVPlayerViewController)ViewController).ShowsPlaybackControls = Element.AreTransportControlsEnabled;
}
void SetSource()
{
AVAsset asset = null;
if (Element.Source is UriVideoSource)
{
string uri = (Element.Source as UriVideoSource).Uri;
if (!String.IsNullOrWhiteSpace(uri))
{
asset = AVAsset.FromUrl(new NSUrl(uri));
}
}
else if (Element.Source is FileVideoSource)
{
string uri = (Element.Source as FileVideoSource).File;
if (!String.IsNullOrWhiteSpace(uri))
{
asset = AVAsset.FromUrl(NSUrl.CreateFileUrl(uri, null));
}
}
else if (Element.Source is ResourceVideoSource)
{
string path = (Element.Source as ResourceVideoSource).Path;
if (!String.IsNullOrWhiteSpace(path))
{
string directory = Path.GetDirectoryName(path);
string filename = Path.GetFileNameWithoutExtension(path);
string extension = Path.GetExtension(path).Substring(1);
NSUrl url = NSBundle.MainBundle.GetUrlForResource(filename, extension, directory);
asset = AVAsset.FromUrl(url);
}
}
if (asset != null)
{
playerItem = new AVPlayerItem(asset);
}
else
{
playerItem = null;
}
player.ReplaceCurrentItemWithPlayerItem(playerItem);
if (playerItem != null && Element.AutoPlay)
{
player.Play();
}
}
// Event handler to update status
void OnUpdateStatus(object sender, EventArgs args)
{
VideoStatus videoStatus = VideoStatus.NotReady;
switch (player.Status)
{
case AVPlayerStatus.ReadyToPlay:
switch (player.TimeControlStatus)
{
case AVPlayerTimeControlStatus.Playing:
videoStatus = VideoStatus.Playing;
break;
case AVPlayerTimeControlStatus.Paused:
videoStatus = VideoStatus.Paused;
break;
}
break;
}
((IVideoPlayerController)Element).Status = videoStatus;
if (playerItem != null)
{
((IVideoPlayerController)Element).Duration = ConvertTime(playerItem.Duration);
((IElementController)Element).SetValueFromRenderer(VideoPlayer.PositionProperty, ConvertTime(playerItem.CurrentTime));
}
}
TimeSpan ConvertTime(CMTime cmTime)
{
return TimeSpan.FromSeconds(Double.IsNaN(cmTime.Seconds) ? 0 : cmTime.Seconds);
}
// Event handlers to implement methods
void OnPlayRequested(object sender, EventArgs args)
{
player.Play();
}
void OnPauseRequested(object sender, EventArgs args)
{
player.Pause();
}
void OnStopRequested(object sender, EventArgs args)
{
player.Pause();
player.Seek(new CMTime(0, 1));
}
}
I don't see your code and can't know what is your problem. I would show you how to make it work about play a local video stored in the document folder:
In xamarin.forms:
public class MyVideoView : View
{
}
And in xaml, simply use this MyVideoView :
<StackLayout>
<!-- Place new controls here -->
<local:MyVideoView HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" />
</StackLayout>
In the iOS project, the custom renderer should be:
public class VideoPlayerRenderer : ViewRenderer
{
AVPlayer player;
AVPlayerItem playerItem;
AVPlayerViewController _playerViewController; // solely for ViewController property
public override UIViewController ViewController => _playerViewController;
protected override void OnElementChanged(ElementChangedEventArgs<View> e)
{
base.OnElementChanged(e);
if (e.NewElement != null)
{
if (Control == null)
{
// Create AVPlayerViewController
_playerViewController = new AVPlayerViewController();
var documents = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
var path = Path.Combine(documents, "iOSApiVideo.mp4");
NSUrl url = NSUrl.CreateFileUrl(path, null);
//if you put your video in the project directly use below code
//NSUrl url = NSBundle.MainBundle.GetUrlForResource("iOSApiVideo", "mp4");
// Set Player property to AVPlayer
player = new AVPlayer(url);
_playerViewController.Player = player;
// Use the View from the controller as the native control
SetNativeControl(_playerViewController.View);
player.Play();
}
}
}
}
Replace the path with your own path there and it will work.
Note:
Doesn't work
NSUrl url = new NSUrl(path);
Work
NSUrl url = NSUrl.CreateFileUrl(path, null);
I am posting the final answer because i ended up using Jack's answer to modify my customRenderer.
so i changed this condition in the SetSource() function in custom Renderer
else if (Element.Source is FileVideoSource)
{
string uri = (Element.Source as FileVideoSource).File;
if (!String.IsNullOrWhiteSpace(uri))
{
var documents = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
var path = Path.Combine(documents, uri);
NSUrl url = NSUrl.CreateFileUrl(path, null);
asset = AVAsset.FromUrl(url);
}
}
the only change is now i am getting the path inside customRenderer and only passing fileName as the parameter from ViewModel. earlier it was full path like [/Users/vaibhavjain/Library/Developer/CoreSimulator/Devices/{GUID}/data/Containers/Data/Application/{GUID}/Documents/MediaDocuments/Nature1.mp4]
I am trying to Implement AutocompleteTextview in xamarin forms,
till now i have achieved the customisation and look which i need to for my Auto Complete Bar,
but the thing where i am stuck is filling of data to Dataadapter from Shared Library also i am not getting what text is selected inside the Entry bar, so could anyone help me how can i bind my list from pcl to native and also i need two way data to Text Property
here is the code which i had implemented till now
my common class
public class AutoCompleteViewv3 : View
{
public AutoCompleteViewv3()
{
}
}
My Android Implementation
[assembly: ExportRenderer(typeof(AutoSuggestBox),
typeof(AutoCompleteViewRendererv3))]
namespace PredictiveList.Droid
{
public class AutoCompleteViewRendererv3 :
ViewRenderer<AutoCompleteViewv3,
AutoCompleteTextView>
{
static string[] COUNTRIES = new string[] {
"Afghanistan", "Albania", "Algeria", "American Samoa", "Andorra",
"Angola", "Anguilla", "Antarctica", "Antigua and Barbuda", };
public AutoCompleteViewRendererv3(Android.Content.Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<AutoCompleteViewv3> e)
{
base.OnElementChanged(e);
if (e.OldElement != null || this.Element == null)
return;
AutoCompleteTextView textView = (AutoCompleteTextView)LayoutInflater.From(Context).Inflate(Resource.Layout.TextEditorLayouts, null);
var adapter = new ArrayAdapter<String>(Context,
Resource.Layout.list_item, COUNTRIES);
textView.Adapter = adapter;
SetNativeControl(textView);
}
// Use the control here.
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (this.Element == null || this.Control == null)
return;
// variable this.Control is the AutoCompleteTextView, so you an manipulate it.
}
}
}
For Ios i am still trying to implemented
please check Code here for Auto complete
I am using Tabbed page in my xamarin forms project.
I am trying to use OnTabReselected event in MyTabsRenderer Class in android. But the events like OnTabSelected, OnTabReselected and OnTabUnselected are not getting called. Does anyone have any solution for this.
xamarin forms version: 3.2.0.871581
VS version : 15.8.6
Here is my code snippet(MyTabsRenderer Class):
using Android.OS;
using Android.Views;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android.AppCompat;
using MyProject;
using MyProject.Droid;
using Xamarin.Forms.Platform.Android;
using System.ComponentModel;
using Android.Support.V4.View;
using Android.Content.Res;
using Android.Content;
using Android.Support.Design.Widget;
[assembly: ExportRenderer(typeof(TabController), typeof(MyTabsRenderer))]
namespace MyProject.Droid
{
public class MyTabsRenderer : TabbedPageRenderer, TabLayout.IOnTabSelectedListener
{
bool setup;
ViewPager viewPager;
TabLayout tabLayout;
public MyTabsRenderer(Context context) : base(context)
{
}
protected override void SetTabIcon(TabLayout.Tab tab, FileImageSource icon)
{
base.SetTabIcon(tab, icon);
}
protected override void OnElementChanged(ElementChangedEventArgs<TabbedPage> e)
{
base.OnElementChanged(e);
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
//if (setup)
// return;
if (e.PropertyName == "Renderer" || e.PropertyName == "CurrentPage")
{
viewPager = (ViewPager)ViewGroup.GetChildAt(0);
tabLayout = (TabLayout)ViewGroup.GetChildAt(1);
setup = true;
ColorStateList colors = GetTabColor();
for (int i = 0; i < tabLayout.TabCount; i++)
{
var tab = tabLayout.GetTabAt(i);
SetTintColor(tab, colors);
}
tabLayout.SetOnTabSelectedListener(new TabLayout.ViewPagerOnTabSelectedListener(viewPager));
}
}
void OnTabReselected(TabLayout.Tab tab)
{
// To have the logic only on he tab on position 1
if (tab == null || tab.Position != 1)
{
return;
}
if (tab.Text == "Play")
{
tab.SetText("Pause");
tab.SetIcon(Resource.Drawable.icon);
// App.pauseCard = false;
}
else
{
tab.SetText("Play");
tab.SetIcon(Resource.Drawable.icon);
// App.pauseCard = true;
}
SetTintColor(tab, GetTabColor());
}
void SetTintColor(TabLayout.Tab tab, ColorStateList colors)
{
var icon = tab?.Icon;
if (icon != null)
{
icon = Android.Support.V4.Graphics.Drawable.DrawableCompat.Wrap(icon);
Android.Support.V4.Graphics.Drawable.DrawableCompat.SetTintList(icon, colors);
}
}
ColorStateList GetTabColor()
{
return ((int)Build.VERSION.SdkInt >= 23)
? Resources.GetColorStateList(Resource.Color.icon_tab, Forms.Context.Theme)
: Resources.GetColorStateList(Resource.Color.icon_tab);
}
}
}
It is probably because you are setting the OnTabSelectedListener wrong.
Try setting the listener to this instead: tabLayout.SetOnTabSelectedListener(this);
Also for inspiration check out TabbedPageRenderer.cs source code,
I want to use a comma as decimal separator in an Xamarin Forms entry with numeric keyboard. I set the Culture to CultureInfo.DefaultThreadCurrentCulture = new CultureInfo("de-DE"); and the , is shown on the keyboard, but the entry only accepts . as separator and this only works if I use the telephone keyboard. This seems to be an Andoid Bug. Is there a solution for Xamarin Forms?
There is.
In order to have it working properly, i would suggest you do the following:
In your shared code create a new class called "NumericInput", deriving from Entry:
public class NumericInput : Entry
{
public static BindableProperty AllowNegativeProperty = BindableProperty.Create("AllowNegative", typeof(bool), typeof(NumericInput), false, BindingMode.TwoWay);
public static BindableProperty AllowFractionProperty = BindableProperty.Create("AllowFraction", typeof(bool), typeof(NumericInput), false, BindingMode.TwoWay);
public NumericInput()
{
this.Keyboard = Keyboard.Numeric;
}
public bool AllowNegative
{
get { return (bool)GetValue(AllowNegativeProperty); }
set { SetValue(AllowNegativeProperty, value); }
}
public bool AllowFraction
{
get { return (bool)GetValue(AllowFractionProperty); }
set { SetValue(AllowFractionProperty, value); }
}
}
Then in your android project create a custom renderer for it:
[assembly: ExportRenderer(typeof(NumericInput), typeof(NumericInputRenderer))]
namespace MyApp.Droid.Renderer
{
public class NumericInputRenderer : EntryRenderer
{
public NumericInputRenderer(Context context) : base(context)
{
}
private EditText _native = null;
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
if (e.NewElement == null)
return;
_native = Control as EditText;
_native.InputType = Android.Text.InputTypes.ClassNumber;
if ((e.NewElement as NumericInput).AllowNegative == true)
_native.InputType |= InputTypes.NumberFlagSigned;
if ((e.NewElement as NumericInput).AllowFraction == true)
{
_native.InputType |= InputTypes.NumberFlagDecimal;
_native.KeyListener = DigitsKeyListener.GetInstance(string.Format("1234567890{0}", System.Globalization.CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator));
}
if (e.NewElement.FontFamily != null)
{
var font = Typeface.CreateFromAsset(Android.App.Application.Context.Assets, e.NewElement.FontFamily);
_native.Typeface = font;
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (_native == null)
return;
if (e.PropertyName == NumericInput.AllowNegativeProperty.PropertyName)
{
if ((sender as NumericInput).AllowNegative == true)
{
// Add Signed flag
_native.InputType |= InputTypes.NumberFlagSigned;
}
else
{
// Remove Signed flag
_native.InputType &= ~InputTypes.NumberFlagSigned;
}
}
if (e.PropertyName == NumericInput.AllowFractionProperty.PropertyName)
{
if ((sender as NumericInput).AllowFraction == true)
{
// Add Decimal flag
_native.InputType |= InputTypes.NumberFlagDecimal;
_native.KeyListener = DigitsKeyListener.GetInstance(string.Format("1234567890{0}", System.Globalization.CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator));
}
else
{
// Remove Decimal flag
_native.InputType &= ~InputTypes.NumberFlagDecimal;
_native.KeyListener = DigitsKeyListener.GetInstance(string.Format("1234567890"));
}
}
}
}
}
this will create an entry element, which automatically uses the correct decimal separator depending on the current culture setting of the device.
since the class allows for some detailed settings, I should also add the iOS renderer:
[assembly: ExportRenderer(typeof(NumericInput), typeof(NumericInputRenderer))]
namespace MyApp.iOS.Renderer
{
public class NumericInputRenderer : EntryRenderer
{
private UITextField _native = null;
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
if (e.NewElement == null)
return;
_native = Control as UITextField;
_native.KeyboardType = UIKeyboardType.NumberPad;
if ((e.NewElement as NumericInput).AllowNegative == true && (e.NewElement as NumericInput).AllowFraction == true)
{
_native.KeyboardType = UIKeyboardType.NumbersAndPunctuation;
}
else if ((e.NewElement as NumericInput).AllowNegative == true)
{
_native.KeyboardType = UIKeyboardType.NumbersAndPunctuation;
}
else if ((e.NewElement as NumericInput).AllowFraction == true)
{
_native.KeyboardType = UIKeyboardType.DecimalPad;
}
else
{
_native.KeyboardType = UIKeyboardType.NumberPad;
}
if (e.NewElement.FontFamily != null)
{
e.NewElement.FontFamily = e.NewElement.FontFamily.Replace(".ttf", "");
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (_native == null)
return;
}
}
}
However keep in mind that some vendors have implemented custom soft keyboards (i am looking at you, Samsung!), that don't even show a comma as a decimal seperator. In that case the only solution is to install another keyboard, such as SwiftKey or Gboard. However, if a comma is shown, you should be able to use it with the code above.
I am trying to write a custom renderer for a Xamarin Map on UWP and a collection change event in the PCL is not triggering the appropriate event in the UWP custom renderer. It works just fine on iOS and Android.
With the following code the event ItemsCollectionChanged never gets called in the CustomMapRenderer even through the OnItemsSourcePropertyChanged is being called every 5 seconds.
public class CustomMap : Map
{
#region << Events >>
public event EventHandler ItemsCollectionChanged;
#endregion
private static void OnItemsSourcePropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
var map = bindable as CustomMap;
if (map == null)
return;
map.ItemsSource.CollectionChanged += (s, e) =>
{
SetPin(bindable);
if (map.ItemsCollectionChanged != null)
{
map.ItemsCollectionChanged(bindable, new EventArgs());
}
};
}
}
[assembly: ExportRenderer(typeof(CustomMap), typeof(CustomMapRenderer))]
Namespace MyNamespace.Renderers
{
public class CustomMapRenderer : MapRenderer
{
MapControl _nativeMap;
protected override void OnElementChanged(ElementChangedEventArgs<Map> e)
{
base.OnElementChanged(e);
if (e.OldElement != null)
{
_nativeMap.MapElementClick -= OnMapElementClick;
_nativeMap.Children.Clear();
_nativeMap = null;
}
if (e.NewElement != null)
{
var formsMap = (CustomMap)e.NewElement;
formsMap.ItemsCollectionChanged += ItemsCollectionChanged;
_pinClickedCommand = formsMap.PinClickedCommand;
_routeCoordinates = formsMap.ItemsSource;
_nativeMap = Control as MapControl;
_nativeMap.Children.Clear();
_nativeMap.MapElementClick += OnMapElementClick;
var snPosition = new BasicGeoposition { Latitude = 45, Longitude = -88 };
Geopoint snPoint = new Geopoint(snPosition);
var mapIcon = new MapIcon();
if (mapIcon != null)
{
_nativeMap.MapElements.Remove(mapIcon);
}
mapIcon.CollisionBehaviorDesired = MapElementCollisionBehavior.RemainVisible;
mapIcon.Location = snPoint;
mapIcon.NormalizedAnchorPoint = new Windows.Foundation.Point(0.5, 1.0);
_nativeMap.MapElements.Add(mapIcon);
_nativeMap.Center = snPoint;
}
}
void ItemsCollectionChanged(object sender, EventArgs e)
{
;
}
}
}
I used a singleton to get the observable collection that I needed