user2621075
user2621075

Reputation: 377

Create Custom UIView From Subclass On Main Thread iOS

I have the following code to a run method that returns a UIImage made from a UIView that's created in separate UIView class file. The FlagMarkerView is a separate subclass of UIView and is referenced here. The problem is the line FlagMarker *markerView... throws the following Main Thread Checker Error and crashes the app.

UIView.init(coder:) must be used from the main thread.

The code used to work as is, but no longer works as I've updated the project to target iOS 11.

I tried wrapping the FlagMarkerView call in a dispatch_async(dispatch_get_main_queue(), ^{}); but that doesn't work because it doesn't get picked up by the return UIImage in the method. I also tried to use a -(void) method instead of returning a UIImage, but that’s hazard with the complexity of my project.

Is there a way I can create the newMarkerImage in - (void)updateMyFlagsWitAlert: and use dispatch_async(dispatch_get_main_queue(), ^{ for FlagMarkerView so newMarkerImage can be created in line from markerImage.

- (void)updateMyFlagsWitAlert:(BOOL)isAllowed{
    for (AGSGraphic *graphic in weakSelf.flagOverlay.graphics) {
        FlagModel *flagToUpdate = graphic.attributes[@"flag"];
           UIImage *newMarkerImage =
                    [weakSelf markerImageForFlag:flagToUpdate withDetail:detail];
    }
}


- (UIImage *)markerImageForFlag:(FlagModel *)flag withDetail:(BOOL)withDetail {

    // This line crashes app
    FlagMarkerView *markerView = [[[NSBundle mainBundle]  loadNibNamed:@"FlagMarkerView" owner:self options:nil] objectAtIndex:0];

    [markerView setFlag:flag];
    UIImage *markerImage = [markerView imageWithDetail:withDetail];
    [markerView layoutIfNeeded];
    return markerImage;
}

enter image description here

Upvotes: 0

Views: 103

Answers (2)

user2621075
user2621075

Reputation: 377

The following code-change solved my problem. I found the problem is not with markerImageForFlag at all even though the Main Thread Checker highlighted the markerView. I believe the weakSelf reference put the task on the background thread, which was ok for awhile, but doesn't work anymore. All I had to do is put the dispatch_sync, not dispatch_async, on the call, newMarkerImage. Thanks to @Rob and @Moshe! See below.

- (void)updateMyFlagsWitAlert:(BOOL)isAllowed{
    for (AGSGraphic *graphic in weakSelf.flagOverlay.graphics) {
        FlagModel *flagToUpdate = graphic.attributes[@"flag"];

        __block UIImage *newMarkerImage = nil;

        dispatch_sync(dispatch_get_main_queue(), ^{        
        newMarkerImage = [weakSelf markerImageForFlag:flagToUpdate withDetail:(scale < markerThreshold)];
        });

    }

}

Upvotes: 0

Moshe Gottlieb
Moshe Gottlieb

Reputation: 4003

You should probably redesign your code to be async, but the quickest way I could think of is something like:

    __block FlagMarkerView *markerView;
    dispatch_sync(dispatch_get_main_queue(), ^{
        markerView = [[[NSBundle mainBundle] loadNibNamed:@"FlagMarkerView" owner:self options:nil] objectAtIndex:0];
        [markerView setFlag:flag];
        UIImage *markerImage = [markerView imageWithDetail:withDetail];
        [markerView layoutIfNeeded];
    });
    return markerImage;

This will make your code run in the main thread, and wait for it to finish.
It is not a good design, you should probably design your code better and not rely on a return value.
Instead, your code could use a block as a parameter that will receive the result after it was processed in the main thread, instead of waiting for it using a semaphore.
But sometimes, if you just need something to work - this solution could be helpful.

Upvotes: 2

Related Questions