Xamarin.Forms - Strange height issue with my responsive WebViews on iOS - xamarin.forms

I have a ListView and inside that I want to bind multiple webviews which all vary in height. I want to calculate the height of each webview based on it's content and display it accordingly.
It works on Android - on iOS all WebViews are FAR too big
await System.Threading.Tasks.Task.Delay(100);
var result = (double)webView.ScrollView.ContentSize.Height;
_webView.HeightRequest = result;
The strange thing is: the MORE html characters the webview has, the bigger it gets. So I could add just one character and another 50px will be added to the height.
It is a lot of code to post here but here is a link to the project on github:
https://github.com/SlimboTimbo/MultipleWebViews

After checking the project , have found the reason why not works in iOS Device .
The reason is that the width of webView.ScrollView.ContentSize is not correct , is too small that is 27 . Therefore , it can not show correctly .
System.Console.WriteLine("----" + resultWidth + "-----"+ resultHeight + "----"+ _webView.Width);
Output :
2020-06-04 11:19:58.066542+0800 MultipleWebViews.iOS[50674:1690400] ----27-----642----375
Solution :
You can caluculate the height by the width of HybridWebView, and set the calcualted height for HybridWebView .
DidFinishNavigation method code as follow :
public override async void DidFinishNavigation(WKWebView webView, WKNavigation navigation)
{
try
{
var _webView = webViewRenderer.Element as HybridWebView;
if (_webView != null)
{
await System.Threading.Tasks.Task.Delay(100);
var resultWidth = (double)webView.ScrollView.ContentSize.Width;
var resultHeight = (double)webView.ScrollView.ContentSize.Height;
System.Console.WriteLine("----" + resultWidth + "-----"+ resultHeight + "----"+ _webView.Width);
double result = MeasureTextHeightSize(_webView.messageContent, _webView.Width, UIFont.LabelFontSize, null);
_webView.HeightRequest = result;
MessagingCenter.Send<Object, PassModel>(this, "LoadFinished", new PassModel(_webView.Id, Convert.ToDouble(result)));
}
}
catch (Exception ex)
{
Console.WriteLine("Error at HybridWebViewRenderer LoadingFinished: " + ex.Message);
}
}
MeasureTextHeightSize ( Calculate the height method )
private double MeasureTextHeightSize(string text, double width, double fontSize, string fontName = null)
{
var nsText = new NSString(text);
var boundSize = new SizeF((float)width, float.MaxValue);
var options = NSStringDrawingOptions.UsesFontLeading | NSStringDrawingOptions.UsesLineFragmentOrigin;
if (fontName == null)
{
fontName = "HelveticaNeue";
}
var attributes = new UIStringAttributes
{
Font = UIFont.FromName(fontName, (float)fontSize)
};
var sizeF = nsText.GetBoundingRect(boundSize, options, attributes, null).Size;
//return new Xamarin.Forms.Size((double)sizeF.Width, (double)sizeF.Height);
return (double)sizeF.Height;
}
The Effect :
Note : You can modify the fontSize of this method according to your current system font size . And also can custom the return value ,such as return (double)sizeF.Height + 10 to fit screen .

Related

How do I render a higher quality image via CustomRenderer in iOS with Xamarin Forms?

This is fairly straight forward, but I have some custom ShellRenderers for my App in Xamarin Forms, and I'm having issues with the scaling of images. In particular, Android looks fantastic with the more pixel-dense image (800x200). It looks great on every screen.
The renderer just ensures that the top of the screen is made of the "theme" color, and is presented in the center of the it. It's that simple/straight-forward.
On iOS, it looks awful and blurred. I'm not sure if there's something I need to set or update (or maybe a different graphics stack?) to make it render with higher pixel density... See my attached renderer:
public class CustomShellRenderer : ShellRenderer
{
#region Methods
protected override IShellSectionRenderer CreateShellSectionRenderer(ShellSection shellSection)
{
var renderer = base.CreateShellSectionRenderer(shellSection);
if (renderer != null)
{
var navigationBar = (renderer as ShellSectionRenderer).NavigationBar;
if (navigationBar != null)
{
navigationBar.BarTintColor = Colors.LightColor.ToUIColor(); //static colors
navigationBar.TintColor = Colors.LightColor.ToUIColor();
using (var logo = UIImage.FromResource(typeof(Images).Assembly, "Path.To.Logo.png"))
{
var statusFrame = UIApplication.SharedApplication.StatusBarFrame;
var navigationFrame = navigationBar.Frame;
var width = Math.Max(navigationFrame.Width, statusFrame.Width);
var fullArea = new CGRect(0, 0, width, navigationFrame.Height + statusFrame.Height);
UIGraphics.BeginImageContext(fullArea.Size);
var backgroundColor = Colors.PrimaryColor.ToUIColor();
backgroundColor.SetFill();
UIGraphics.RectFill(fullArea);
var logoArea = new CGRect(width * .35, statusFrame.Height, width * .3, navigationFrame.Height);
logo.Draw(logoArea);
var backgroundImage = UIGraphics.GetImageFromCurrentImageContext();
UIGraphics.EndImageContext();
(renderer as ShellSectionRenderer).NavigationBar.SetBackgroundImage(backgroundImage, UIBarMetrics.Default);
backgroundImage.Dispose();
}
}
}
return renderer;
}
#endregion
}
Any thoughts? Right now, the problem is that when it renders, there is an 800x200 image trying to squeeze into... 140x30 or so space on some emulators...
Thank you!

Xamarin Forms - Image To/From IRandomAccessStreamReference

For personal needs, for the Xamarin.Forms.Map control, I need to create a CustomPin extension. UWP part (PCL project)
I create a MapIcon like it:
nativeMap.MapElements.Add(new MapIcon()
{
Title = pin.Name,
Image = RandomAccessStreamReference.CreateFromUri(new Uri("ms-appx:///Assets/Pin/customicon.png")),
Location = new Geopoint(new BasicGeoposition() { Latitude = pin.Position.Latitude, Longitude = pin.Position.Longitude }),
NormalizedAnchorPoint = new Windows.Foundation.Point(0.5, 1.0)
});
However, by this way, I can't set the Image's size.
I then want to use an Image from my PCL part, resize it and convert it into a IRandomAccessStreamReference. To realize it, I need to convert my Image into a stream, but I can't find the way to make it works ><
Example of the function needed:
private IRandomAccessStreamReference ImageToIRandomAccessStreamReference(Image image)
{
//Here I can set the size of my Image
//I convert it into a stream
IRandomAccessStreamReference irasr = RandomAccessStreamReference.CreateFromStream(/* img? */);
//irasr is then created from img
//I return the IRandomAccessStreamReference needed by the MapIcon element
return irasr;
}
Note: The Image paramter img is a Xamarin.Forms.Image
So first, is it possible? If yes, then thank for any help which could help me.. I already search about how to resize the MapIcon and it's not possible directly from the class [MapIcon].(https://msdn.microsoft.com/library/windows/apps/windows.ui.xaml.controls.maps.mapicon.aspx)
Thank for help !
You are right. We can't resize the MapIcon directly as it doesn't provide such properties or methods. MapIcon's size is mostly controlled by the size of image which is set by MapIcon.Image property. And we can set this image's size without using Xamarin.Forms.Image.
To set this image's size, we can take advantage of BitmapDecoder class, BitmapEncoder class and BitmapTransform class like following:
private async System.Threading.Tasks.Task<RandomAccessStreamReference> ResizeImage(StorageFile imageFile, uint scaledWidth, uint scaledHeight)
{
using (IRandomAccessStream fileStream = await imageFile.OpenAsync(FileAccessMode.Read))
{
var decoder = await BitmapDecoder.CreateAsync(fileStream);
//create a RandomAccessStream as output stream
var memStream = new InMemoryRandomAccessStream();
//creates a new BitmapEncoder and initializes it using data from an existing BitmapDecoder
BitmapEncoder encoder = await BitmapEncoder.CreateForTranscodingAsync(memStream, decoder);
//resize the image
encoder.BitmapTransform.ScaledWidth = scaledWidth;
encoder.BitmapTransform.ScaledHeight = scaledHeight;
//commits and flushes all of the image data
await encoder.FlushAsync();
//return the output stream as RandomAccessStreamReference
return RandomAccessStreamReference.CreateFromStream(memStream);
}
}
And then we can use this method to create a resized image stream reference first and then set it as MapIcon's Image like:
var file = await StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///Assets/Pin/customicon.png"));
var imageReference = await ResizeImage(file, 64, 64);
nativeMap.MapElements.Add(new MapIcon()
{
Title = pin.Name,
Image = imageReference,
Location = new Geopoint(new BasicGeoposition() { Latitude = pin.Position.Latitude, Longitude = pin.Position.Longitude }),
NormalizedAnchorPoint = new Windows.Foundation.Point(0.5, 1.0)
});

how to add css to selected row in treegrid GXT 3

I created a treegrid using GXT 3.now iwant to change background color of selected row and also i want to change the background of root node(leaf row i.e Parent row).
iam using GXT 3.0 and eclipse 3.7
Thanks in advance
I was also having the same problem, I wanted to color the background of a row depending on some condition. In the end, I found a solution:
You need to create a GridViewConfig and override the getColumnStyle method to return the color want, it took me a while to find out, but overriding the getRowStyle method doesn't do the trick, at least not for me.
grid.getView().setViewConfig(new GridViewConfig<Model>() {
#Override
public String getColStyle( Model model,
ValueProvider<? super Model, ?> valueProvider,
int rowIndex, int colIndex)
{
if ("Other2".equals(model.getName())){
return "bold";
}else if ("Other".equals(model.getName())){
return "red-row";
}
return null;
}
#Override
public String getRowStyle(Model model, int rowIndex) {
return null;
}
});
Note: Modify CSS file accordingly.
I think it is the same with GXT 2.x
Just add your own CellRenderer on your ColumnConfig :
List<ColumnConfig> columns = new ArrayList<ColumnConfig>();
ColumnConfig levelColumnConfig = new ColumnConfig("level", "Level", 50);
levelColumnConfig.setRenderer(getGridCellRenderer());
columns.add(levelColumnConfig);
ColumnModel cm = new ColumnModel(columns);
private GridCellRenderer<BeanModel> getGridCellRenderer() {
if (gridCellRenderer == null) {
gridCellRenderer = new GridCellRenderer<BeanModel>() {
#Override
public Object render(BeanModel model, String property, ColumnData config, int rowIndex, int colIndex,
ListStore<BeanModel> store, Grid<BeanModel> grid) {
//add some logic for example
String color = "#000000";
MyBean mybean = (MyBean) model.getBean();
switch (mybean.getLevel()) {
case TRACE:
color = "#f0f0f0";
break;
default:
color = "#000000";
}
// add some background-color
config.style = config.style + ";background-color:" + color + ";";
Object value = model.get(property);
return value;
}
};
}
return gridCellRenderer;
}
I found out that you also can adjust CSS like this:
grid.getCellFormatter().addStyleName(0, colNo - 1, MC_GWT.MC_STYLE_VERTICAL_ICON_HOLDER);
It's a little easier i think :) Especially when you want to style the , f.e. alignment of icons inside of it. In my case i needed to display icons in a vertical row. So i needed to change the Cells display to 'block';

Flex 4.5 mobile Resize textarea with animation on soft keyboard activate event?

I'm using Flex 4.5.1 with AIR 2.7 (and Flash Builder 4.5.1) to build an app for the Blackberry Playbook.
The app has a large textarea that needs to be resized when the soft keyboard shows up. I am able to hook into the soft keyboard events and do things there.
Now, I want to resize the textarea to fit the remaining part of the screen when the keyboard shows up. I know the current and destination size and want to use a smooth animation to show the textarea resizing. (I can't already set the height and can see the textarea being correctly resized in the keyboard_activating event - but I want to do it through an animation). I've tried using the Animate class, the spark.effects.Move class and none of them seem to work in this case. They seem to run the animation but the screen does not get refreshed!
I don't want to use the built-in resizeAppForKeyboard property on the ViewNavigatorApplication. I have set to 'none' in the app descriptor so that part of it is fine.
Any ideas / thoughts? Is my approach correct at all? How would one go about resizing the textarea using an animation in the keyboard activating event? My code looks like:
In the main view (onCreationComplete event): (txNote is the textarea in question)
txNote.addEventListener(SoftKeyboardEvent.SOFT_KEYBOARD_ACTIVATE, function(e:SoftKeyboardEvent):void {
toggleEditMode(true);
});
txNote.addEventListener(SoftKeyboardEvent.SOFT_KEYBOARD_DEACTIVATE, function(e:SoftKeyboardEvent):void {
toggleEditMode(false);
});
private function toggleEditMode(keyboardActivated:Boolean):void {
trace("toggle edit: " + editMode + ", kb activating: " + keyboardActivated + ", txNote height = " + txNote.height);
editMode = keyboardActivated;
//we handle resize manually, because we want a nice animation happening and we want to resize only the text area - nothing else.
var y:Number = editMode ? -38 : 0;
var height:Number = editMode ? 218 : 455;
txNote.moveAndSize(y, height);
}
The code in txNote.moveAndSize:
public function moveAndSize(newY:Number, newHeight:Number):void {
//parent group uses BasicLayout
//(this.parent as Group).autoLayout = false;
//resizePath.valueFrom = this.height;
//resizePath.valueTo = newHeight;
//movePath.valueFrom = this.y;
//movePath.valueTo = newY;
//resizeEffect.heightFrom = this.height;
//resizeEffect.heightTo = height;
this.top = newY;
moveEffect.xFrom = this.x;
moveEffect.xTo = this.x;
moveEffect.yFrom = this.y;
moveEffect.yTo = newY;
moveEffect.end();
moveEffect.play([ this ]);
//this.move(x, newY);
//animate.play([ this ]);
//this.height = height;
//this.y = y;
//setLayoutBoundsSize(width, height);
//setLayoutBoundsPosition(x, y);
}
The moveEffect / movePath / animate various things I tried are set up in the txNote constructor as follows:
public class NotesArea extends TextArea {
// private var animate:Animate;
// private var movePath:SimpleMotionPath;
// private var resizePath:SimpleMotionPath;
private var moveEffect:Move;
// private var resizeEffect:Resize;
public function NotesArea() {
super();
// animate = new Animate();
// var paths:Vector.<MotionPath> = new Vector.<MotionPath>();
// movePath = new SimpleMotionPath("top");
// resizePath = new SimpleMotionPath("height");
// paths.push(movePath);
//paths.push(resizePath);
// animate.duration = 300;
// animate.motionPaths = paths;
// animate.addEventListener(EffectEvent.EFFECT_UPDATE, function(e:EffectEvent):void {
// trace("y = " + y);
// invalidateDisplayList();
// });
// animate.addEventListener(EffectEvent.EFFECT_END, function(e:EffectEvent):void {
// trace("EFFECT ended: y = " + y);
// });
moveEffect = new Move();
moveEffect.duration = 250;
moveEffect.repeatCount = 1;
moveEffect.addEventListener(EffectEvent.EFFECT_END, function (e:EffectEvent):void {
//(this.parent as Group).autoLayout = true;
trace("move effect ran. y = " + y + ", top = " + top);
});
//this.setStyle("moveEffect", moveEffect);
//
// resizeEffect = new Resize();
// resizeEffect.duration = 250;
// this.setStyle("resizeEffect", resizeEffect);
}
}
yourTextField.addEventListener(SoftKeyboardEvent.SOFT_KEYBOARD_DEACTIVATE, onActivating);
yourTextField.addEventListener(SoftKeyboardEvent.SOFT_KEYBOARD_ACTIVATING, onActivating);
yourTextField.addEventListener(SoftKeyboardEvent.SOFT_KEYBOARD_ACTIVATE, onActivating);
// in the event listener to the textField IE :
private function onActivating(event:SoftKeyboardEvent):void
{
//listen to the event; make your move here.
}

AIR: component behaves wrong after switching window

Ok so I have a component, that has a function to remove itself as a popUp in its current Window, and add itself to a newly created Window.
It works, however, if the component has a child like a ComboBox, the drop down still pops up in the old window where it used to be, also scrollbars, and focus seems to behave incorrectly in the new window also.
It seems to me like Flex still thinks the component is a child of the original window, not the new window. I have no idea how to resolve this though.
Here is my code:
private var ownWindow:Window;
private var _inOwnWindow:Boolean;
private var _removedEffect:Move;
private var _openX:Number;
private var _openY:Number;
public function launchInNewWindow(e:Event):void
{
_openX = Application.application.nativeWindow.x + this.x + 5; //keep in same spot add 5 for systemChrom border
_openY = Application.application.nativeWindow.y + this.y + 30;//keep in same spot add 30 for systemChrom title
this.parent.removeChild(this);
ownWindow = new Window();
ownWindow.systemChrome = 'none';
ownWindow.type = NativeWindowType.LIGHTWEIGHT;
ownWindow.transparent = true;
ownWindow.setStyle('showFlexChrome', false);
ownWindow.width = this.width > 750 ? 750 : this.width;
ownWindow.height = this.height > 550 ? 550 : this.height;
edit.enabled = false;
_removedEffect = this.getStyle('removedEffect') as Move;
if(_removedEffect == null)
{
openNewWindow();
}
else
{
// Wait for removed effect to play before adding to new window
_removedEffect.addEventListener(EffectEvent.EFFECT_END,delayOpenInNewWindow);
}
}
private function delayOpenInNewWindow(e:Event = null):void
{
var t:Timer = new Timer(100,1);
t.addEventListener(TimerEvent.TIMER,openNewWindow);
t.start();
}
private function openNewWindow(e:Event = null):void
{
ownWindow.addChild(this);
ownWindow.width += 5; //add to show dropshadow
ownWindow.height += 10; //add to show dropshadow
ownWindow.open();
_inOwnWindow = true;
ownWindow.nativeWindow.x = _openX;
ownWindow.nativeWindow.y = _openY;
}
Any ideas?
Thanks!!
Before I give this a run, have you tried a callLater on the openNewWindow() line?
[ lame fix attempt, i know -- but given that there doesn't seem to be an event that you can listen for in the case that the removedEffect isn't null and it seems like a timer is your only option there, I think it's o.k. to give lame fix attempts :-) ]

Resources