Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does UIView.StringSize need to run on the UI thread?

Tags:

xamarin.ios

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?

Edit (2013-01-17):

I filed a bug with Xamarin just to see their response. It sounds like they are looking into marking this method as ThreadSafe.

like image 984
patridge Avatar asked Dec 22 '25 16:12

patridge


1 Answers

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);
    }
});
like image 153
jonathanpeppers Avatar answered Dec 24 '25 09:12

jonathanpeppers