Creating a non-displayed, sized, UIView outside the UI thread? - xamarin.ios

Is there a way to manipulate the size of a non-displayed UIView from outside the UI thread prior to adding it to a displayed view?
While working through some asynchronous iOS code, I thought I would try to have an async method build up a UIView that would be displayed later [on the UI thread]. In this case, and this appears to be the "gotcha", it was a UILabel where I want to give it a predetermined frame size derived from a StringSize call. Unfortunately, the UIView constructor that takes a RectangleF frame calls UIApplication.EnsureOnUIThread first.
// Throws UIKitThreadAccessException on Frame-setting UILabel constructor.
Task<UILabel> getView = Task.Factory.StartNew(() => {
//... Do some async fun (e.g., call web service for some data for someNSString)
SizeF requiredStringSize = someNSString.StringSize(someFont, new SizeF(maxWidth, float.MaxValue), UILineBreakMode.WordWrap);
RectangleF someViewFrame = new RectangleF(PointF.Empty, requiredStringSize)
return new UILabel(someViewFrame);
});
Since I don't really need to set a valid location at the point of this task execution, I figured I could avoid setting the frame in the constructor and set the size afterwards. Unfortunately, you only seem to be able to set size by modifying UIView.Frame as a whole. While the parameter-less constructor does not make this UI thread call, as soon as I try to set the Frame to the size needed, the UIView.Frame accessor does and it blows up.
// Also throws UIKitThreadAccessException, this time when setting the Frame directly.
Task<UILabel> getView = Task.Factory.StartNew(() => {
//...do all the above stuff...
UIView someView = new UILabel();
someView.Frame = new RectangleF(someView.Frame.Location, requiredStringSize);
});
I've already decided to make my code more specific to the case at hand and use a Task<string> instead, letting the displaying code (run on the UI thread) handle the view creation, but it would be nice to know if this is possible since it would make the code I am writing more extensible.

UIKit is not designed to be used outside of the main thread.
I've seen some bizarre behaviour creep in when this rule is ignored, so I strongly advise against this.

Related

MVVM Light DispatcherHelper, correct way to multithread in MVVM Light

What is the recommended way to do multithreading with MVVM Light.
I have a model which has a bool property Busy
public bool Busy
{
get { return busy_; }
set
{
Set(nameof(Busy), ref busy_, value, broadcast: true);
}
}
My view model publish the model directly for the view (the model is inherit MVVM Light's ViewModelBase), so the view binds directly to the model's busy property.
If I call the model always from the UI thread everything is good. But if I do the following in my view model so it may execute on a different thread
Task.Factory.StartNew(() =>
{
model_.SomeFunctionThatWillSetBusyDuringItsExecution();
});
Then of course Busy is set from a non UI thread and then the binding fails and the application crashes. If I happen to use the Messenger in the property setter, it seems the Messenger does not automatically dispatch the Messenger handler code to the UI thread either.
I realized there is a DispatcherHelper in MVVM Light, but for the binding it does not seem to help. If I change the property to
public bool Busy
{
get { return busy_; }
set
{
DispatcherHelper.CheckBeginInvokeOnUI(() =>
{
Set(nameof(Busy), ref busy_, value, broadcast: true);
});
}
}
I still get an exception and the application crash due to the binding source is not on the correct thread. So my question is simple, what is the recommended way to do multithreading like this in MVVM Light?
I did also try to use a syncronizationContext.
syncContext_.Post(() =>
{
Set(nameof(Busy), ref busy_, value, broadcast: true);
}, null);
That works if the call is always from a non UI-thread. If the call is already from the UI thread, the syncContext.Post results in that the Set() function is not called until all the code in the ViewModel method has finished. That means the busy state might not be updated correctly for the remaining code. So it is not an ideal solution.
I am thankful for help on this topic.
Instead of adding the DispatcherHelper code inside the property I added it at all places where the property was modified. Then it seems to work well.
Only problem, since one dispatch the work to the UI thread, the code in the ViewModel would not get the updated state if part of the view model method already runs on the UI thread. I found a way to force the UI thread to process its messenger queue though making sure it got the updated state of Busy. It is not the best looking solution, and it is likely to have a bad performance impact due to all context switching, but at least it works and it is a simple one liner.
Code to force the UI thread to process all messages in its queue
DispatcherHelper.UIDispatcher.Invoke(new Action(() => { }), DispatcherPriority.ContextIdle, null);
If there is a more optimal way to solve it then please let me know. Otherwise I will set this as the answer in a few days from now.

When are the bounds and font of a UITextView initialized in Xamarin Forms?

I'm trying to implement an Editor with hint text functionality for a Xamarin.Forms project. This is trivial in Android, because the underlying EntryEditText control has a Hint property. In iOS, the implementation is a bit more complex because the UITextView class does not implement hint text.
I don't like the technique, "set text to the placeholder, clear it if typing starts, return it if typing ends and the text is blank". It means I have to do extra work to tell if the control's blank, and there's a lot of fiddling with the text color involved. But I've been having so much trouble I'm going to have to resort to it. Maybe someone can help me with this.
I started with the answer to Placeholder in UITextView. I started a new Xamarin iOS project and stumbled through a rough Obj-C to C# conversion, and it worked great with a minor change: the Font property of the UITextView isn't initialized yet in the constructor, so I had to override AwakeFromNib() to set the placeholder label's font. I tested it and it worked, so I brought that file into a Xamarin Forms project, and things started getting a little nutty.
The first problem is it turns out apparently MonoTouch has some slight API differences in Xamarin Forms, such as using some types like RectangleF instead of CGRect. This was obvious, if not unexpected. I've been wrestling with some other differences for the past few days, and can't seem to overcome them in a way that makes me happy. Here's my file, trimmed down significantly because I've been trying all kinds of debugging things:
using System;
using MonoTouch.UIKit;
using MonoTouch.Foundation;
using MonoTouch.CoreGraphics;
using System.Drawing;
namespace TestCustomRenderer.iOS {
public class PlaceholderTextView : UITextView {
private UILabel _placeholderLabel;
private NSObject _notificationToken;
private const double UI_PLACEHOLDER_TEXT_CHANGED_ANIMATION_DURATION = 0.25;
private string _placeholder;
public string Placeholder {
get {
return _placeholder;
}
set {
_placeholder = value;
if (_placeholderLabel != null) {
_placeholderLabel.Text = _placeholder;
}
}
}
public PlaceholderTextView() : base(RectangleF.Empty) {
Initialize();
}
private void Initialize() {
_notificationToken = NSNotificationCenter.DefaultCenter.AddObserver(TextDidChangeNotification, HandleTextChanged);
_placeholderLabel = new UILabel(new RectangleF(8, 8, this.Bounds.Size.Width - 16, 25)) {
LineBreakMode = UILineBreakMode.WordWrap,
Lines = 1,
BackgroundColor = UIColor.Green,
TextColor = UIColor.Gray,
Alpha = 1.0f,
Text = Placeholder
};
AddSubview(_placeholderLabel);
_placeholderLabel.SizeToFit();
SendSubviewToBack(_placeholderLabel);
}
public override void DrawRect(RectangleF area, UIViewPrintFormatter formatter) {
base.DrawRect(area, formatter);
if (Text.Length == 0 && Placeholder.Length > 0) {
_placeholderLabel.Alpha = 1;
}
}
private void HandleTextChanged(NSNotification notification) {
if (Placeholder.Length == 0) {
return;
}
UIView.Animate(UI_PLACEHOLDER_TEXT_CHANGED_ANIMATION_DURATION, () => {
if (Text.Length == 0) {
_placeholderLabel.Alpha = 1;
} else {
_placeholderLabel.Alpha = 0;
}
});
}
public override void AwakeFromNib() {
base.AwakeFromNib();
_placeholderLabel.Font = this.Font;
}
protected override void Dispose(bool disposing) {
base.Dispose(disposing);
if (disposing) {
NSNotificationCenter.DefaultCenter.RemoveObserver(_notificationToken);
_placeholderLabel.Dispose();
}
}
}
}
A notable change here is relocation of the label's initialization from DrawRect() to the constructor. As far as I can tell, Xamarin never lets DrawRect() be called. You'll also note I'm not setting the Font property. It turned out in the iOS MonoTouch project, sometimes the parent's font was null and it's illegal to set the label's font to null as well. It seems at some point after construction Xamarin sets the font, so it's safe to set that property in AwakeFromNib().
I wrote a quick Editor-derived class and a custom renderer so Xamarin Forms could render the control, the Renderer is slightly of note because I derived from NativeRenderer instead of EditorRenderer. I needed to call SetNativeControl() from an overridden OnModelSet(), but peeking at the assembly viewer showed that EditorRenderer makes some private calls I'll have to re-implement in mine. Boo. Not posted because this is already huge, but I can edit it in if needed.
The code above is notable because the placeholder isn't visible at all. It looks like in iOS-oriented MonoTouch, you typically initialize a control with a frame, and resizing is a rare enough circumstance you can assume it doesn't happen. In Xamarin Forms, layout is performed by layout containers, so a constructor-provided frame is irrelevant. However, the size of the label is intended to be set in the constructor, so it ends up having negative width. Whoops.
I assumed this could be solved by moving instantiation of the label into AwakeFromNib(), or at least sizing it there. This is when I discovered that for some reason, AwakeFromNib() isn't called in the control. Welp. I tried to find an equivalent callback/event that happened late enough for the bounds to be set, but couldn't find anything on the iOS side. After trying many, many things, I noticed the custom renderer received property change events for the Xamarin Forms Model side of this mess. So, if I listen for Height/Width change events, I can then call a method on the label to give it a reasonable size based on the current control. That exposed another problem.
I cannot find a way to set the label's font to match the UITextView's font. In the constructor, the Font property is null. This is true in both the iOS and Xamarin Forms project. In the iOS project, by the time AwakeFromNib() is called, the property is initialized and all is well. In the XF project, it's never called, and even when I pull stunts like invoking a method from a 5-second delayed Task (to ensure the control is displayed), the property remains null.
Logic and iOS documentation dictates the default value for the font should be 17-point Helvetica. This is true for the placeholder label if I fudge the size so it's visible. It is not true for the UITextView control, though since it reports its font as null I'm unable to see what the font actually is. If I manually set it all is well, of course, but I'd like to be able to handle the default case. This seems like a bug; the box seems to be lying about its font. I have a feeling it's related to whatever reason the Xamarin.Forms.Editor class doesn't have a Font property.
So I'm looking for the answer to two questions:
If I'm extending an iOS control in XF to add a subview, what is the best way to handle sizing that subview? I've found Height/Width changes raise events in the renderer, is this the only available way?
When the property has not been set by a user, is the Font of a UITextView in Xamarin Forms ever set to a non-null value? I can live with a requirement that this control requires the font to be explicitly set, but it's yucky and I'd like to avoid it.
I'm hoping I've missed something obvious because I started barking up the wrong trees.
If I'm extending an iOS control in XF to add a subview, what is the
best way to handle sizing that subview? I've found Height/Width
changes raise events in the renderer, is this the only available way?
This is the only way I know of since the exposed elements of the renderer are so limited.
When the property has not been set by a user, is the Font of a
UITextView in Xamarin Forms ever set to a non-null value? I can live
with a requirement that this control requires the font to be
explicitly set, but it's yucky and I'd like to avoid it.
No, the Font is not assigned a default non-null value.

XNA 4 GraphicsDevice

From my few years of experience programming in graphics, one thing that I have learned is that you should never pass in a reference to a graphics context to an object and operate on it for the duration of the program (JOGL explicitly states this). A context can be invalidated when something such as the graphics device (GPU) is reset, turned off, or some other weird thing happens.
I have recently delved back into programming in XNA 4.0, and one of my projects involves objects needing to know about the size of the window/viewport, when the window is resized, and when dynamic buffers have lost their content (requiring the buffers to be rebuilt on a possibly invalidated GraphicsDevice). Instead of passing in the GraphicsDevice and GameWindow to numerous methods in the update phase or for Disposal, I have opted to pass them into constructors. For example:
public Camera(GameWindow w, GraphicsDeviceManager m) {
// ... Yada-yada matrices
gdm = m;
window = w;
window.ClientSizeChanged += OnWindowResize;
}
public void Dispose() {
window.ClientSizeChanged -= OnWindowResize;
window = null;
gdm = null;
}
// Control Logic ...
public void OnWindowResize(object Sender, EventArgs args) {
Vector2 s = new Vector2(gdm.GraphicsDevice.Viewport.TitleSafeArea.Width, gdm.GraphicsDevice.Viewport.TitleSafeArea.Height);
// Recalculate Projection ...
}
Is it safe to do something like this, or is something happening in the background that I need to be aware of?
I solved this problem in my current game project by running the game as a singleton, which makes it available in a static context within the namespace. Game.Instance.graphicsDevice will always point to the current graphics device object, even if the context has changed. XNA raises various events when the context is invalidated/changed/reset/etc., and you can reload/re-render things and resize buffers as needed by hooking in to these events.
Alternatively, you could pass GraphicsDevice with the ref keyword, which might be a quick, drop-in fix by simply being the same reference as the original caller, assuming that caller that instantiated your objects either has the original reference object or had the GraphicsDevice passed to it with ref as well.

Does every monotouch UIKit variable need to be at class scope?

In this Xamarin forum post, Xamarin admin Clancey says:
But as a general rule, anything that has that type of interaction say
a UIButton, you really need to keep a reference to it.
For example:
//This can will blow up
public override ViewDidLoad()
{
var myButton = new UIButton();
myButton.Tapped += delegate{
//do something
};
View.AddSubview(myButton);
}
This is because myButton goes out of scope and is GC'ed, but the underlying iOS button handle is still alive, and if ObjectiveC calls back to Mono with that button handle, the app crashes. So you need to make myButton a class level variable, so it stays alive.
My question is: what about other kinds of UIKit objects, like UIImage? Suppose I do this:
var image = new UIImage(sFilename);
ctlImageView = new UIImageView(image);
this.View.Add(ctlImageView);
ctlImageView is at class scope, but image isn't. Can this cause the same kind of memory leak, and does image also need to be at class scope? Or are UIView objects special in this regard?
You don't need to keep a reference to the UIImage because the UIImageView will hold one for you and clean it up when it is disposed.

Monotouch: how to optimize the performance for manual load the NIB

I use the scrollview to display multipage view, which have more than 10 pages. (scrollview.PageEnabled=true)
And every single page in scrollview has about 6 sub-view(named:ABCUI) which every one is loaded from nib :
this.scrollview.DecelerationEnded+= scrollView_DecelerationEnded(...);
public void LoadSubView(int nPageNo)
{
if (this.PageLoaded(nPageNo))
return;
for (int i=0;i<6;i++)
{
ABCViewController abcUI=MonoTouch.Foundation.NSBundle.MainBundle.LoadNib ("ABCUI", this, null); //XIB file size: 20K
abcui.SetTitle(...);
abcui.SetXXXX(...);
abcui.frame = .. pageFrame.X += this.ScrollView.Frame.Width+nPage*...;
this.scrollview.addsubview(abcUI.view,...);
}
}
public void scrollView_DecelerationEnded (object sender, EventArgs e)
{
int nPageNo=(int)Math.Ceiling((this.ScrollView.ContentOffset.X+1)/this.ScrollView.Frame.Width);
this.LoadSubView(nPageNo +1);
this.LoadSubView(nPageNo - 1);
}
public void Button1Clicked(object sender, EventArgs e)
{
this.ClearViewsInScrollView();
this.LoadSubView(1);
}
When the user trigger the button1 click, it will load the first page into scrollview(only 1 page oncetime, but 1 page has 6 sub-view), and when user scroll the scrollview , it will load the next page.
But it will take a long time when load the first page or switch page in scrollview , so the user must waiting:
ipad1: about 1000ms
iPad2: about 600ms
in simulator: 100ms;
how to optimize the performance(reduce to less 300ms/ipad1)?
Very good question and excellent timing, since I have been working on something like this the past few days.
Now, I am not sure if this solution will get you < 300ms loading, however in theory it is faster.
(You'll see both "XIB" and "NIB" terms. I am referring to the same thing. After all, a NIB is a "compiled" XIB.)
The key to the whole thing is to prevent loading of each XIB file multiple times. There is no reason for it, since what you (we) basically need from it, are instances from the objects in the XIBs, and not the XIBs themselves occupying memory.
Fortunately, the iOS SDK provides the UINib class which can do what we want. With this class, we can create multiple instances of the contents of a XIB, without having to load the XIB itself each time, just once in the "beginning".
Here's how to do it:
First, create a UINib object for each XIB file you want.
// Loads a NIB file without instantiating its contents.
// Simplified here, but to have access to a NIB for the whole lifecycle of the app,
// create a singleton somewhere.
static UINib abcuiNib = UINib.FromName("ABCUI", NSBundle.MainBundle);
Second, after you have loaded the NIB into memory, you can now get the objects from it.
abcuiNib.InstantiateWithOwneroptions(this, new NSDictionary());
Note the "this" parameter. Since it is a view controller you want to load, the above line should be somewhere early in the object's life cycle, eg in the constructor:
public partial class ABCViewController : UIViewController
{
public ABCViewController()
{
// The first parameter is needed to set an owner (File's Owner) for the objects
// that will be instantiated.
// The second parameter is for various options. It does not accept null in MonoTouch,
// but you can just pass an empty NSDictionary.
// If you have all your outlets correctly set up, the following line is all
// that is needed.
abcuiNib.InstantiateWithOwneroptions(this, new NSDictionary());
}
// We don't need the following, as it will load the XIB every time
//public ABCViewController() : base("ABCUI", null) {}
// view controller implementation
}
Mind you, I have not tested the above, as so far I have tested it with various single objects in XIBs. If (and when) I use it with UIViewControllers in XIBs, this is the direction I will move in. I'll also prepare an article with more in-depth findings and info in due time.
Also note that the same "rules" apply, eg. you will still have to release all outlets in the ViewDidUnload override.
If after this, you do not find any improvement in performance, I think you will need to redesign your XIBs. Apple suggests it is better to have multiple XIBs with few objects in each one, instead of few XIBs packed with objects.
Useful reading: Apple docs on NIB management

Resources