If I run UIView.StringSize from within some async code like a Task.ContinueWith, it will blow up with a UIKitThreadAccessException because that method starts with a call to UIApplication.EnsureUIThread ("Go to declaration" in MonoDevelop; I'm not sure the license allows me to post it here).
Task.Factory.StartNew(() => {
// Blows up discretely...nothing written to application output.
// Also blows up for UIViews that exist only in code (not rendered).
SizeF textSize = someView.StringSize(someString, someFont, new SizeF(someView.Bounds.Width, float.MaxValue), UILineBreakMode.WordWrap);
Console.WriteLine(textSize);
});
If I wrap this simplified version in InvokeOnMainThread, all is well, but I definitely have times when I want to measure some text without that call. As well, I fully understand the exception's purpose and it has saved me a bunch of hassle when called something deep within async code before, but in this case the use of EnsureUIThread here seems unnecessary. If I simply restate that call as a hit on the NSString class, it will happily run outside the UI thread.
Task.Factory.StartNew(() => {
// Outputs expected size data: "{Width=##, Height=##}".
using (NSString nssSomeString = new NSString(someString)) {
SizeF textSize = nssSomeString.StringSize(someFont, new SizeF(someView.Bounds.Width, float.MaxValue), UILineBreakMode.WordWrap);
Console.WriteLine(textSize);
}
});
The code for UIView.StringSize appears to do roughly the same NSString work and there doesn't appear to be anything blatantly UI-thread-oriented. Is there something I am missing that would require this version of the method to be called from the UI thread?
I filed a bug with Xamarin just to see their response. It sounds like they are looking into marking this method as ThreadSafe.
It is because your UIView is being access from a thread it wasn't created on. Since you are creating NSString in the background thread, your second example will work fine and is recommended. (Although I don't think you should try to access someView.Bounds from a background thread either)
I suspect this would also work:
var bounds = someView.Bounds; //UI thread
Task.Factory.StartNew(() => {
// Outputs expected size data: "{Width=##, Height=##}".
using (UIView view = new UIView()) {
SizeF textSize = view.StringSize(someString, someFont, new SizeF(bounds.Width, float.MaxValue), UILineBreakMode.WordWrap);
Console.WriteLine(textSize);
}
});
But I would just stick with NSString:
var bounds = someView.Bounds; //UI thread
Task.Factory.StartNew(() => {
// Outputs expected size data: "{Width=##, Height=##}".
using (NSString nssSomeString = new NSString(someString)) {
SizeF textSize = nssSomeString.StringSize(someFont, new SizeF(bounds.Width, float.MaxValue), UILineBreakMode.WordWrap);
Console.WriteLine(textSize);
}
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With