In my Xamarin Forms app I have a WebView used as a form by inserting a div with contentEditable set to true. It all worked fine until the update to AppCompat styles.
Now, a strange white overlay (white square) appears when the user tries to write inside the WebView, that disappears when the keyboard is dismissed. In order to find the root cause of the problem I have created a sample project with just one page containing just a vertical stack layout containing another stack layout and the webview. What I have noticed is that this problem happens if the height of the internal stack layout is enough for the webview to be covered by the keyboard when it appears. In this case the webview is pushed up, and this strange overlay appears. The webview is fully functional, and it is possible to write text, even though it is hidden by this white square, that disappears when the keyboard is dismissed.
This is an example of a simple page that can be used to test the issue. The buttons have been added only to increase and decrease the size of the layout to show the problem more easily
public class MainPage : ContentPage
{
const string ContentEdit = #"<div contentEditable=""true"" style =""min-height: 200px"">";
const string ContentEditEnd = "</div>";
const string EmptyDocument = #"<body>" + ContentEdit + ContentEditEnd + #"</body>";
WebView webView;
public MainPage()
{
InitializePage();
}
void InitializePage()
{
webView = new WebView()
{
VerticalOptions = LayoutOptions.FillAndExpand,
HorizontalOptions = LayoutOptions.FillAndExpand,
};
var button1 = new Button
{
HorizontalOptions = LayoutOptions.CenterAndExpand,
VerticalOptions = LayoutOptions.CenterAndExpand,
Text = "-",
};
var button2 = new Button
{
HorizontalOptions = LayoutOptions.CenterAndExpand,
VerticalOptions = LayoutOptions.CenterAndExpand,
Text = "+",
};
var tallLayout = new StackLayout()
{
Orientation = StackOrientation.Horizontal,
HeightRequest = 200,
BackgroundColor = Color.Lime,
Children = { button1, button2, },
};
button1.Clicked += (sender, e) => tallLayout.HeightRequest = tallLayout.Height - 20;
button2.Clicked += (sender, e) => tallLayout.HeightRequest = tallLayout.Height + 20;
var layout = new StackLayout
{
Orientation = StackOrientation.Vertical,
VerticalOptions = LayoutOptions.FillAndExpand,
HorizontalOptions = LayoutOptions.FillAndExpand,
Children = { tallLayout, webView },
};
Content = layout;
}
protected override void OnAppearing()
{
InitializeEmptyContent();
}
public void InitializeEmptyContent()
{
var htmlSource = new HtmlWebViewSource();
htmlSource.Html = EmptyDocument;
webView.Source = htmlSource;
}
}
I have tried to reproduce this simple layout also just using native Android, but it seems I don't have any similar bug. Is this a Xamarin Forms bug or am I doing something wrong?
In the end the problem was recognised as a Xamarin bug.
Related
I have been following the custom pin tutorial using Xamarin.Forms which I have linked below. I have finished implementing it and I have also moved onto adding pins to the map as well through tapping.
https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/custom-renderer/map/customized-pin
(side note: I am working almost exclusively with Xamarin.Forms and Android)
Currently, I am able to tap on the map and a new custom pin will be added at that location, which is great. What is not great is that I was unable to figure out how to get a tap and long hold gesture to work instead of just the normal tap. To try to combat this, and because I will eventually have to add these anyways, I am planning on adding a button that the user can press to initiate that they want to add a button to the map, and I will later add an undo button, and many others, etc.
The problem is, I have no idea how to get the result of what my toggle button state is from the custom render that I am using for the map. Where can I get the result of my toggle button and use it as a boolean towards whether to add a button or not?
Here is the toggle button code, which I took line by line from their example on this page:
https://learn.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/button
Here is the code where I add a custom pin just by a single tap:
private void GoogleMap_MapClick(object sender, GoogleMap.MapClickEventArgs e)
{
((CustomMap)Element).OnTap(new Position(e.Point.Latitude, e.Point.Longitude));
var addingPin = new CustomPin
{
Type = PinType.Place,
Position = new Position(e.Point.Latitude, e.Point.Longitude),
Address = " - need to possibly implement - ",
Id = "shelter",
Label = "Pin from tap",
Url = "http://www.redcross.org"
};
Map.Pins.Add(addingPin);
this.customPins.Add(addingPin);
}
I thought about making a custom button render but by my knowledge I can only apply one Android render to a content page at a time, and when I tried to add a custom button render to the map render then this method was not happy, as it was taking in some sort of Android Map View and not a button view:
protected override void OnElementChanged(Xamarin.Forms.Platform.Android.ElementChangedEventArgs<Map> e)
{
base.OnElementChanged(e);
if (e.OldElement != null)
{
NativeMap.InfoWindowClick -= OnInfoWindowClick;
}
if (e.NewElement != null)
{
var formsMap = (CustomMap)e.NewElement;
customPins = formsMap.CustomPins;
Control.GetMapAsync(this);
}
}
Any help is greatly appreciated. Below I have included a pic of what my application looks like so far, along with the custom page that I am using as well.
using Hermes.Models;
using System;
using System.Collections.Generic;
using Xamarin.Forms;
using Xamarin.Forms.Maps;
namespace Hermes
{
public class MapPage : ContentPage
{
public MapPage()
{
CustomMap customMap = new CustomMap()
{
HeightRequest = 100,
WidthRequest = 960,
VerticalOptions = LayoutOptions.FillAndExpand,
MapType = MapType.Street,
};
var examplePinSupplies = new CustomPin
{
Type = PinType.Place,
Position = new Position(42.02525, -93.65087),
Address = " - need to possibly implement - ",
Id = "supplies",
Label = "supplies",
Url = "https://www.redcross.org/store"
};
var examplePinMedical = new CustomPin
{
Type = PinType.Place,
Position = new Position(42.02290, -93.63912),
Address = " - need to possibly implement - ",
Id = "medical",
Label = "medical",
Url = "http://www.redcross.org"
};
var examplePinShelter = new CustomPin
{
Type = PinType.Place,
Position = new Position(42.02045, -93.60968),
Address = " - need to possibly implement - ",
Id = "shelter",
Label = "shelter",
Url = "http://www.redcross.org"
};
customMap.CustomPins = new List<CustomPin> { examplePinSupplies, examplePinMedical, examplePinShelter };
customMap.Pins.Add(examplePinSupplies);
customMap.Pins.Add(examplePinMedical);
customMap.Pins.Add(examplePinShelter);
customMap.MoveToRegion(MapSpan.FromCenterAndRadius(new Position(42.025250, -93.650870), Distance.FromMiles(1.0)));
var addPin = new ToggleButton { Text = "Add pin" };
var buttons = new StackLayout
{
Orientation = StackOrientation.Horizontal,
Children = {
addPin
}
};
Content = new StackLayout
{
Spacing = 0,
Children = {
customMap,
buttons
}
};
}
}
}
What I am trying to do: I am trying to tap the 'add pin' toggle button, and then the map will allow me to only add one pin on the map by tapping, and then any other consecutive taps that are not on another pin will not add another pin to the map.
I have a scanner app build in Xamarin for Android and IOS.
When the scanner succeed I want to show a green screen of 0,5 seconds.
And when it fails I want to show a red screen.
But I can't find any code that allows me to create that screen.
I hope anyone here can push me in the right direction.
I think you will have a callback function when scanner succeed or fails.
In the callback function you can use BackgroundColor = Color.Red; to change the background.
Every page have the BackgroundColor property. For example I created a bottom by code and click the button to change the page background:
public App()
{
InitializeComponent();
var page1 = new ContentPage();
Button changeBgBt = new Button { Text = "change backgroud color", WidthRequest = 100, HeightRequest = 50, VerticalOptions=LayoutOptions.Center, HorizontalOptions = LayoutOptions.Center };
changeBgBt.Clicked += ChangeBgBt_Clicked;
var content = new StackLayout();
content.Children.Add(changeBgBt);
page1.Content = content;
MainPage = page1;
}
private void ChangeBgBt_Clicked(object sender, EventArgs e)
{
MainPage.BackgroundColor = Color.Red;
}
I am trying to play the GIF images with the Xamarin.Forms (Portable) project. I have implemented with the following code but unable to Play GIF, I don't event see the static image.
There is no error or crash in this code, but I am not able to see the
GIF image.
Is there anything wrong in the code?
Interface:
public interface IGif
{
string Get();
}
iOS Implementation:
[assembly: Dependency(typeof(GifView_iOS))]
namespace Project.iOS
{
public class GifView_iOS : IGif
{
public string Get()
{
return NSBundle.MainBundle.BundlePath;
}
}
}
Android Implementation:
[assembly: Dependency(typeof(GifView_Droid))]
namespace Project.Droid
{
public class GifView_Droid : IGif
{
public string Get()
{
return "file:///android_asset/";
}
}
}
GIF Image class:
public class Gif: WebView
{
public string GifSource
{
set
{
string imgSource = DependencyService.Get<IGif>.Get() + value;
var html = new HtmlWebViewSource();
html.Html = String.Format
(#"<html><body style='background: #000000;'><img src='{0}' /></body></html>",
imgSource);
SetValue(SourceProperty, html);
}
}
}
And Finally, I have added to the StackLayout.
Gif gifimage = new Gif();
gifimage.GifSource = "loading.gif";
StackGif.Children.Add(gifimage);
I have not tested this code in iPhone, maybe it is working in iPhone.
Thank You in advance.
This was the silly mistake, I have to give the size of Image inside web view and to GIF class also.
Here is the updated code.
My GIF Class:
public class Gif: WebView
{
public string GifSource
{
set
{
string imgSource = DependencyService.Get<Dependency.IGif>().Get() + value;
var html = new HtmlWebViewSource();
html.Html = String.Format(#"<html><body><img src='{0}' style='width: 100px; height: 100px;' /></body></html>", imgSource);
SetValue(SourceProperty, html);
}
}
}
and Initialization in ContentPage:
Helpers.Controls.Gif gifImage = new Helpers.Controls.Gif();
gifImage.GifSource = "loading.gif";
gifImage.HorizontalOptions = LayoutOptions.Center;
gifImage.VerticalOptions = LayoutOptions.FillAndExpand;
gifImage.HeightRequest = 120;
gifImage.BackgroundColor = Color.Transparent;
GridLoad.Children.Add(gifImage);
Note: I am still working on the Background Part, as it is looking like
attached image, I want the transparent background in web view.
Image:
By default StackLayout occupies the whole available space but WebView doesn't.
Instead just
Gif gifimage = new Gif();
use
public WebViewProgrammaticallyPage()
{
var StackGif = new StackLayout()
{
BackgroundColor=Color.Red,
};
Gif gifimage = new Gif()
{
WidthRequest = 120,
HorizontalOptions = LayoutOptions.CenterAndExpand,
HeightRequest = 80,
VerticalOptions = LayoutOptions.CenterAndExpand,
};
gifimage.GifSource = "icon1.png";
StackGif.Children.Add(gifimage);
Content = StackGif;
}
Transparency of WebView is different problem. You should use custom renderers
https://forums.xamarin.com/discussion/34957/creating-a-webview-with-a-transparent-background
Right now i am implementing a custom Renderer for my Xamarin.Forms Label with which i can make use of the marquee scrolling features of the TextView.
What i want to do now is change the scrolling mode of the text from just going to the left endlessly to "bouncing" from side to side. (Kind of like this question i found regarding this subject: How to make a label scroll a word back and forth?) I have not found any resources online that talk about this. I would be very happy if you could give me a general idea of where to look or what to do.
Thank you for your time
Here is my marquee Custom-Renderer:
protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
{
MarqueeLabel marqLabel = (MarqueeLabel)this.Element;
base.OnElementChanged(e);
if (marqLabel.shouldScroll)
{
Control.SetSingleLine(true);
Control.SetHorizontallyScrolling(true);
Control.Selected = true;
Control.Ellipsize = Android.Text.TextUtils.TruncateAt.Marquee;
Control.SetMarqueeRepeatLimit(-1);
}
}
If you can do the animations on the Xamarin.Forms level then you can save yourself quite a bit of work rather than implementing on each platform-specific level.
Try the following example that demonstrates some bouncing of a Label to and from the edges of the screen, as it may give you some ideas? Note it doesn't do it continuously in the example below.
StackLayout objStackLayout = new StackLayout()
{
Orientation = StackOrientation.Vertical,
};
Label objLabel = new Label();
objLabel.Text="Hello";
objLabel.HorizontalOptions = LayoutOptions.Start;
objStackLayout.Children.Add(objLabel);
Button objButton1 = new Button()
{
Text = "Animate"
};
objButton1.Clicked += (async (o2, e2) =>
{
double dblWidth = objStackLayout.Width - objLabel.Width;
//
await objLabel.TranslateTo(dblWidth, 0,1000, Easing.BounceIn);
await objLabel.TranslateTo(0,0,1000, Easing.BounceIn);
});
objStackLayout.Children.Add(objButton1);
This is not the good way to do that but I am sure this will solve your problem.
StackLayout objStackLayout = new StackLayout()
{
Orientation = StackOrientation.Vertical,
};
Label lblMarquee = new Label();
lblMarquee.Text = "This is marquee animation!!!";
lblMarquee.HorizontalOptions = LayoutOptions.Start;
objStackLayout.Children.Add(lblMarquee);
Button btnAnimate = new Button()
{
Text = "Stare Marquee Animation"
};
btnAnimate.Clicked += (async (o2, e2) =>
{
double dblWidth = objStackLayout.Width + lblMarquee.Width;
// Infinite loop
while (true)
{
lblMarquee.IsVisible = false;
await lblMarquee.TranslateTo(dblWidth / 2, 0, 500, Easing.SinIn);
lblMarquee.IsVisible = true;
await lblMarquee.TranslateTo(-(dblWidth / 2), 0, 10000, Easing.SinIn);
}
});
objStackLayout.Children.Add(btnAnimate);
Content = objStackLayout;
}
NOTE: You can remove the infinite loop and try the same with TPL that will be good way to handle never ending task instead of using infinite loop.
I'm trying to prepare mobile app that runs on Android and iOS using Titanium Appcelerator
In one of the view, i'll send a request to server, get response and after parsing it, i'll create a scroll view with internal views and populate the data accordingly. The app is working fine on iPhone. But in Android, its not working peoperly. Unless and until i add the controls to windows, they are not being shown in Android.
I'd like to know if I've to set any thing to work properly in android. The following code is used to add the subviews to scroll view:
var xPos = 0;
for (var i = 0; i < cityCodes.length; i++)
{
xPos = i*320;
var cityImg = require('screens/views').cityDataView(xPos, cityClicked,
cityCodes.item(i).text,
cityNames.item(i).text,
cityExportQuantities.item(i).text,
exportPercentages.item(i).text,
populationCount.item(i).text,
popultionPercentages.item(i).text,
rates.item(i).text
);
scrlView.add(cityImg);
}
I'm creating the scroll view as following:
scrlView = Titanium.UI.createScrollView({
contentWidth:'auto',
contentHeight:'auto',
top:'40%',
height:'155dp',
showVerticalScrollIndicator:true,
showHorizontalScrollIndicator:false
});
I'm creating the city data view as following:
cityDataView: function(xPos, showCityDetails, cityCode, cityName, exportQuantity, exportPercent, popCount, popPercent, rate)
{
var tempLabel = function(topPos,txt){
// Ti.API.log(topPos);
var custLabel = Ti.UI.createLabel({
color:'#000',
text:txt,
right:'55%',
top:topPos,
font:{
fontSize:14,
fontFamily:'GE SS Text Light'
},
textAlign:Titanium.UI.TEXT_ALIGNMENT_RIGHT,
width:'auto'
});
return custLabel;
};
var cityImg = Ti.UI.createImageView({
left:xPos,
height : '155dp',
width : '100%',
image : '/images/citydetails.png'
});
cityImg.addEventListener('singletap', function(){
Ti.API.log(cityCode);
showCityDetails(cityCode);
});
var cityNameLbl = Ti.UI.createLabel({
color:'#ffffff',
text:cityName,
height:'15dp',
top:'6%',
font:{
fontSize:14,
fontFamily:'GE SS Text Light'
},
textAlign:Titanium.UI.TEXT_ALIGNMENT_RIGHT,
width:'auto'
});
cityImg.add(cityNameLbl);
var exportQtyLbl = tempLabel('19%',exportQuantity);
cityImg.add(exportQtyLbl);
var overallProd = tempLabel('35%',exportPercent);
cityImg.add(overallProd);
var populationCount = tempLabel('53%',popCount);
cityImg.add(populationCount);
var populationPercent = tempLabel('69%',popPercent);
cityImg.add(populationPercent);
var rateText = tempLabel('85%',rate);
cityImg.add(rateText);
// var totalExportText = tempLabel('95%',totalExport);
// cityImg.add(totalExportText);
return cityImg;
}