4

In iOS app widget, I can see on only some devices, doubled data (see figure below). I have tried to identify device, iOS version, but it seems to be "random". Plus, I am unable to debug this by myself, because on every of my devices, all is rendered correctly and doing blind debugging is not working (several updates on AppStore but still with the same error).

In widget, I download (in background thread) new data from web and put them (in dispatch_get_main_queue()) into labels, images etc. All is working OK, but sometimes the old data are not "cleared". In my design file for widget, I have cleared all "default" texts, so this is not this problem.

enter image description here

Doubled icon & texts 4.1°C and 7.9°C are overlapping

Main part of my widget code is (shortened by removing other labels, tables and geolocation):

- (void)viewDidLoad
{
    [super viewDidLoad];
    if ([self.extensionContext respondsToSelector:@selector(widgetLargestAvailableDisplayMode)])
    {
        //this is iOS >= 10                
        self.extensionContext.widgetLargestAvailableDisplayMode = NCWidgetDisplayModeExpanded;
    }


    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(FinishDownload:) name:@"FinishDownload" object:nil];


    self.preferredContentSize = CGSizeMake(320, 160);

     [self updateData];
}

-(void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];

    [self updateData];
}

-(void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];

    [self updateData];
}

-(void)updateData
{

    [[[DataManager SharedManager] settings] Reload];


    [[CoreDataManager SharedManager] reset];

    if ([[DataManager SharedManager] DownloadDataWithAfterSelector:@"FinishDownload"] == NO)
        {
            //no need to download update - refill data now
            //if downloading - wait for download
            [self FillData];
        }
    }


}


-(void)FinishDownload:(NSNotification *)notification
{
    dispatch_async(dispatch_get_main_queue(), ^{
        [self FillData];
    });

}

-(void)FillData
{
    //a lot of code - example of setting temperature
    NSString *str = [NSString stringWithFormat:@"%@ °C", act.temp_act];
    self.lblTemp.text = str;
    [self.lblTemp sizeToFit];


    if (self.completionHandler != nil)
    {
        self.completionHandler(NCUpdateResultNewData);
    }
}



- (void)widgetPerformUpdateWithCompletionHandler:(void (^)(NCUpdateResult))completionHandler
{
    // Perform any setup necessary in order to update the view.

    // If an error is encountered, use NCUpdateResultFailed
    // If there's no update required, use NCUpdateResultNoData
    // If there's an update, use NCUpdateResultNewData

    //completionHandler(NCUpdateResultNewData);
    NSLog(@"=== widgetPerformUpdateWithCompletionHandler === ");

    self.completionHandler = completionHandler;

    [self updateData];


}

- (UIEdgeInsets)widgetMarginInsetsForProposedMarginInsets:(UIEdgeInsets)defaultMarginInsets
{
    return UIEdgeInsetsMake(0, 0, 5, 5);
}


- (void)widgetActiveDisplayModeDidChange:(NCWidgetDisplayMode)activeDisplayMode withMaximumSize:(CGSize)maxSize
{
    if (activeDisplayMode == NCWidgetDisplayModeExpanded)
    {
        self.preferredContentSize = CGSizeMake(320, 160);            
    }
    else if (activeDisplayMode == NCWidgetDisplayModeCompact)
    {
        self.preferredContentSize = maxSize;            
    }
}
Martin Perry
  • 9,232
  • 8
  • 46
  • 114
  • Update your question with relevant code if you expect any one to be able to help you solve this issue. – rmaddy Nov 07 '16 at 18:29
  • @rmaddy added code – Martin Perry Nov 07 '16 at 18:45
  • 1
    I can see there you call two times [self FillData]; First try to remove one of them and check the result. Then try to wrap both calls in dispatch_async(dispatch_get_main_queue(), ^{ [self FillData]; }); – Vladimír Slavík Nov 21 '16 at 14:18
  • 1
    `lblTemp` sounds like it's just a temporary label; is there also another label? without seeing the context it's hard to pinpoint your problem. Also, you splurge calls to `updateData`: viewDidLoad, willAppear, didAppear—that's a bit of a code smell. – PDK Nov 21 '16 at 23:20
  • @PDK It is not temporary, its short for `LblTemperature` – Martin Perry Nov 23 '16 at 07:26
  • Are you programatically adding the label to the view? If so, please show the code. It looks to me like you are adding a new label with clear background above an existing one. – shallowThought Nov 24 '16 at 14:23
  • @shallowThought No.. all UI is created in storyboard, there are no dynamic UI elements – Martin Perry Nov 24 '16 at 14:25
  • I am surprised to read in your comment that you created the UI in the storyboard, since you have code to explicitly invoke `sizeToFit`. Autolayout not working for you? Updated my answer – SwiftArchitect Nov 27 '16 at 18:02

3 Answers3

1
  1. View Lifecycle

    Do not duplicate the work in viewDidLoad and viewWillAppear/viewDidAppear.
    A view that was loaded will hit all three methods. Use viewDidLoad for operations that must be performed exactly once for the life of the UIViewController.
    Potential problem:
    Triggering 3 invocations, possibly conflicting, to [self updateData] back to back, possibly with competing NCUpdateResult completion handlers3.

  2. Balance Observers

    It appears that addObserver is never balanced by a removeObserver. A good location for these registration methods is a set of balanced messages, such as the view___Appear and view___Disappear methods, as outlined in this StackOverflow answer.
    Potential problem:
    Lasting registration to notifications on objects that may go out of scope.

  3. Do not cache OS handlers

    Possible misuse of NCUpdateResultNewData completion handler: the NCUpdateResult is passed to widgetPerformUpdateWithCompletionHandler to be used for that specific invocation, not stored for multiple reuse. It should probably be handed down to updateData as a parameter rather than stored in a global, in turn passed to FillData, and eventually cleared after a one-time use.

    if (nil != self.completionHandler) {
        self.completionHandler(NCUpdateResultNewData);
        self.completionHandler = nil; // One time use
    }
    

    Every invocation to widgetPerformUpdateWithCompletionHandler has its own cycle, as outlined in this StackOverflow answer.

  4. Layout & Autolayout

    Be aware that the iOS is making a snapshot of your widget ; in Interface Builder, make sure that you use proper layering of views. Pay special attention to transparency and drawing flags. Leverage Autolayout to resize/size/snap objects

Community
  • 1
  • 1
SwiftArchitect
  • 47,376
  • 28
  • 140
  • 179
1

Check the UILabel's options in Interface Builder, make sure 'opaque' is unchecked. If the label is set as opaque, it might not be properly clearing the entire view when you change the text. You probably want to check on the 'clears graphics context' property as well, which should be checked.

Chad Podoski
  • 958
  • 9
  • 11
1

In the code you add a Notification observer. You do not remove the observer.

I suspect that the notification will be fired multiple times which will result jn a race condition or something.

Solution: - check hoe often the addObserver is executed. (Including screen changes like back-forward etc)

  • remove the observer when the notification is caught.

  • clear / remove the observer when leaving the VC

Besides: check / reduce the action in the ViewWillAppear and ViwDidAppear.

Vincent
  • 4,342
  • 1
  • 38
  • 37