I seem to be having the exact opposite problem that pops up my question searches. I have a splitViewController that seems to be having a slow time updating its master view, after I call setNeedsDisplay. Eventually the update request gets drawn, but it is randomly between 5 to 150 seconds after the change should occur.
If I immediately rotate the iPad, the view changes are immediately reflected.
The layout is:
SVC - Detail VC
\
+-Navigation VC
\
MasterVC
+--UILabel (hidden/unhidden)
|
+--UIButton
All I want to do is hide/unhide a label in the MastVC when an action takes place in the MasterVC. On viewDidLoad, the label is hidden. When a button is pushed on the MasterVC, the label is unhidden and then things just don't go.
I have set everything under the sun to "setNeedsDisplay", but nothing makes it happen as fast as it should. If I even pushed all the setNeedsDisplay methods into dispatch_async(dispatch_get_main_queue(), ^{ ... }; there are no immediate results (not that I'm on a different thread, but it seemed like a good thing to try after reading similar questions).
I have made these calls from the SplitVC, the NavVC, the Master, each subVC, each subView, I even set up a Notification Center call from the Master to the SVC to have the SVC do the update specifically after the label was flagged as unhidden.
This all started to seem exceedingly off track, just to show/hide a simple label. Especially when all I have to do for the label show up properly is to just rotate the iPad.
As I said, the label eventually shows in the right spot, so it isn't off frame or opaque = 0 or something like that.
When I push the connect button, I make a call to Bluetooth Central Manager. Once the BCM connects to the device, I get a NC key/value that confirms connection. This triggers the label to be unhid.
-(void) receiveBCMNotification: (NSNotification *) notification {
NSDictionary *userInfo = notification.userInfo;
NSLog(#"got a BCM notice: %#",userInfo);
if ([[userInfo allKeys] containsObject:ddkBltCentralManagerStatusKey]) {
if ([[userInfo valueForKey:ddkBltCentralManagerStatusKey] isEqualToString:ddvBltCentralMangerScanningStarted]) {
[self.refreshAvailablePatches beginRefreshing];
}
if ([[userInfo valueForKey:ddkBltCentralManagerStatusKey] isEqualToString:ddvBltCentralMangerScanningEnded]) {
[self.refreshAvailablePatches endRefreshing];
[self.availablePatchesTableView reloadData];
}
if ([[userInfo valueForKey:ddkBltCentralManagerStatusKey] isEqualToString:ddvBltCentralMangerDeviceConnected]) {
self.connectedToPatchVisual.hidden = NO;
[self.view setNeedsDisplay];
NSDictionary *newInfo = [NSDictionary dictionaryWithObject:ddvMasterSideViewNeedsDisplay forKey:ddkMasterSideViewNeedsDisplay];
[[NSNotificationCenter defaultCenter] postNotificationName: ncMasterSideNotifications object:nil userInfo:newInfo];
}
}
}
Related
I'm using the NSNotification centre to detect changes of currency so I can update all the other classes. When a currency change occurs, all the other classes and views get updated, however when there is no currency change, and if you press the back button to go back to the home page the view loads on top of the already existing view.
Code for NSNotification center
if ([overviewModel.currency isEqual:#"GBP"]){
[[NSNotificationCenter defaultCenter] postNotificationName:#"DataUpdated"
object:self];
} else {
[[NSNotificationCenter defaultCenter] postNotificationName:#"DataUpdated"
object:self];
}
Code for handling updated data in homepage:
for (UIView *b in self.view.subviews) {
[b removeFromSuperview];
}
self.build = [[ApiRequestBuild alloc]initWithVersionKey:kAPI_VERSION_KEY requestType:kAPI_REQUEST_TYPE data:#""];
[self.build setQueryWithSection:#"homepage" value:#"" parameter:#[]];
self.request = [[ApiRequest alloc]init];
self.request.delegate = self;
[self.request sendRequestWithParams:[self.build buildConfig] toUrl:kAPI_URL_STRING];
I know why this is happening, the request gets sent again so the page loads on top of the already existing page, what I don't understand is why doesn't the remove from subview code get rid of the of the view and how would I be able to fix this? thanks
The removeFromSuperview won't work if it's being called from another thread (than main thread). Your notification will be received on the same thread it was fired from. I'll wager that you're listening to a model change event (regarding your currency state) on another thread.
Try dispatching to main queue before walking your copy of subviews to remove them all.
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.
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;
}
}
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.
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.
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.
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
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.
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.
I'm examining some code written by someone else, as part of a new job I've been hired for. One of the bugs confronting me is that, on certain pages, when the MBProgressHUD is displayed for "Logging In" or "Connecting," it gets a chunk taken out of the middle. For example:
Obviously what I'm looking for is something a little more like this:
It only seems to happen when the app returns from the background (i.e. not when the app is first booting up, when we also use MBProgressHUD but it works perfectly), and only on certain pages. The box loads correctly, and then about half a turn of the Activity Indicator later, that hole appears. It then stays like that until the box disappears.
I'd add some code to look at, but to be perfectly honest, I don't know where to start. I can't think of anything that could take a chunk out of the middle like that, and as you can see from the transparency of the second picture, there doesn't seem to be a box of that shape/size behind the Activity Indicator that could be accidentally turning green.
I've never used MBProgressHUD myself before, and I've never encountered a graphical bug of this nature. Does anyone know what is going on, or failing that, can anyone give me some leads to investigate regarding what could be causing this behavior?
EDIT:
Below is the code used to add the Activity Indicator to the HUD (from within the MBProgressHUD object):
// Update to indeterminate indicator
[self.indicator removeFromSuperview];
self.indicator = nil;
if (IOSVersion >= 8.0 && (DeviceScreenSize().height >= 1136.0 || DeviceScreenSize().width >= 1136.0)) {
self.indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
} else {
self.indicator = MB_AUTORELEASE([[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge]);
}
[(UIActivityIndicatorView *)indicator startAnimating];
[self addSubview:indicator];
This is part of a larger updateIndicators method, but the salient points are there: the indicator is removed (in case there was another there before), then it is re-added and animated. Comment out either of the startAnimating or addSubview lines, and the HUD appears without the Activity Indicator, but the problem never occurs.
That sounds to me like the animation of the Activity Indicator is somehow causing the missing piece of the underlying view. But why would that be? Has anyone heard of that kind of thing before?
EDIT 2:
As far as I can tell, the problem only happens on one ViewController in the whole app, and yet that ViewController never references MBProgressHUD or Activity Indicators of any kind. And all of that functionality is in the AppDelegate method applicationDidBecomeActive:, as below:
MBProgressHUD* hud = [[MBProgressHUD alloc] initWithWindow:self.window];
[self.window addSubview:hud];
hud.labelText = LocalizedString(#"Logging in...");
[hud showAnimated:YES whileExecutingBlock:^{
User* U = self.SelectedUser;
if (!isEmpty(U)) {
if ([U networkLogin]) {
[self setSelectedUser:U];
if ([U Disabled] != disabledTypesNone) {
[Flurry logEvent:#"Login Failed" withParameters:#{#"Name": U.DisplayName,
#"DeviceID": [#([Device sharedDevice].DeviceID) stringValue],
#"Disabled": [#([U Disabled]) stringValue]}];
ret = NO;
} else {
[Flurry logEvent:#"Login" withParameters:#{#"Name": U.DisplayName,
#"DeviceID": [#([Device sharedDevice].DeviceID) stringValue]}];
[Flurry setUserID:[NSString stringWithFormat:#"%# - %#", U.EmpID, U.DisplayName]];
}
} else {
ret = NO;
}
}
} completionBlock:^{
[hud removeFromSuperview];
if (!ret) {
[[NSNotificationCenter defaultCenter] postNotificationName:kDisplayLogin object:nil];
} else {
[[NSNotificationCenter defaultCenter] postNotificationName:kLoggedIn object:nil];
}
}];
Since it's happening in AppDelegate, it ought to be the same throughout the app, correct? And yet, when that same code is called (from AppDelegate, as before) while the app is on a different ViewController, it works with no problems.
What might make the behavior of that method different between different ViewControllers?
I'm having some really weird behavior in my UIViewController. I'm connecting two devices via the Multipeer Framework and once a iPhone starts sending data I want the other iPhone to display a UIViewController which handles the received data. So once iPhone A starts sending, iPhone B posts a NSNotification. The notification is arriving pretty fast and the code below is being executed:
- (void)presentPeerView:(NSNotification *)notif
{
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
ClientViewController *clientView = (ClientViewController *)[storyboard instantiateViewControllerWithIdentifier:#"peerView"];
[clientView setImage:_image];
clientView.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self.window.rootViewController presentViewController:clientView animated:YES completion:nil];
}
Now here comes the weird part. First of all, the ClientViewController gets displayed. But there is no animation, it just appears out of nowhere. My viewDidLoad gets called and runs following code:
- (void)viewDidLoad
{
[self.view setBackgroundColor:[UIColor colorWithPatternImage:[self blurImage:_image];
[self.imageView setImage:_image];
}
Now, I see the blurred image, which is fine. But the (non blurred) image dosen't displays in the UIImageView. I mean the image get's passed properly, otherwise I couldn't see the blurred Background.
I also have a few Interface elements from the Storyboard:
So the UIViewController loads with the blurred background Image:
As you can see, none of the Storyboard UI Elements are loaded, except for the UISwitch (which looks quite strange, too).
After a couple of seconds (it depends, sometimes up to 30, sometimes only 3 seconds) the whole UI loads up:
So, maybe someone of you had the same issue before, I really don't understand why the UI does take so much time to "load". The CPU percentage is somewhere around 3%.
I also added a NSLog() to the this completion block:
[self.window.rootViewController presentViewController:clientView animated:YES completion:^{
NSLog(#"done.");
}];
which is called instantly. The viewDidLoad seems to work fine, there are no lines it might get stuck, if I do a NSLog at the end of viewDidLoad it gets called instantly as well, but the view (as you can see in the images below) isn't loaded (completely).
From the MCSession class reference:
Important: Delegate calls occur on a private operation queue. If your app needs to perform an action on a particular run loop or operation queue, its delegate method should explicitly dispatch or schedule that work.
You're seeing this delay because you're doing UIView work on the wrong thread.
There's a number of ways to fix this, for example:
- (void)session:(MCSession *)session didReceiveData:(NSData *)data fromPeer:(MCPeerID *)peerID {
dispatch_async(dispatch_get_main_queue(), ^{
// post your notification or display your view
});
}
Currently, I'm downloading data from a web server by calling a method fetchProducts. This is done in another separate thread. As I successfully download fifty items inside the method stated above, I post a notification to the [NSNotification defaultCenter] through the method call postNotificationName: object: which is being listened to by the Observer. Take note that this Observer is another ViewController with the selector updateProductsBeingDownloadedCount:. Now as the Observer gets the notification, I set the property of my progressView and a label that tells the progress. Below is the code I do this change in UI.
dispatch_async(dispatch_get_main_queue(), ^{
if ([notif.name isEqualToString:#"DownloadingProducts"]) {
[self.progressBar setProgress:self.progress animated:YES];
NSLog(#"SetupStore: progress bar value is %.0f", self.progressBar.progress);
self.progressLabel.text = [NSString stringWithFormat:#"Downloading %.0f%% done...", self.progress * 100];
NSLog(#"SetupStore: progress label value is %#", self.progressLabel.text);
[self.view reloadInputViews];
}
});
The idea is to move the progressView simultaneously as more items were being downloaded until it is finished. In my case, the progressView's animation will just start right after the items were already downloaded, hence a delay. Kindly enlighten me on this.