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.
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.
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];
}
}
}
I have a UIButton with a titleLabel, for example, like this: #"Download it!"
I would like after my download is finished, update the titleLabel of my button with another text, for example, like this: #"Already downloaded!"
I can change the state (enable or not) but impossible to refresh / update the titleLabel of the UIButton.
Any idea how to do this ? I tried [myButton setNeedsDisplay]; but it doesn't work.
Thank for your suggestions and help.
UPDATE 1:
Solution:
[yourButton setTitle:<#(NSString *)#> forState:<#(UIControlState)#>]
Have you tried this?
[yourButton setTitle:<#(NSString *)#> forState:<#(UIControlState)#>]
You can change the text in a button's title label.
[aButton setTitle:#"Already downloaded!" forState:UIControlStateNormal];
For more information on the topic, and the full list of control states, see this: https://developer.apple.com/library/ios/documentation/uikit/reference/uicontrol_class/reference/reference.html#//apple_ref/doc/c_ref/UIControlState
All the example explained with this post explains the change of title on button's various state like UIControlStateNormal, UIControlStateHighlightedbut it is not doing it on download complete.
The simplest way is to keep notify your viewController that the some process (the download) is finished. Then you change the button Title as required.
May be try this code.
Add a button & a Notification observer in your ViewController viewDidLoad as
self.someButton.title = #"Download Now"; // set the button title
// Add notification Observer
[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(notifyDownloadComplete:)
name:#"DOWNLOAD_COMPLETE"
object:nil];
Now define the target method of the Observer to perform the Button title Change as
-(void)notifyDownloadComplete:(NSNotification*)note {
self.someButton.title = #"Already Downloaded";
}
Now add a Download Method via GCD & then post the notification once it is completed.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//Here your non-main thread. Try Downloading something
dispatch_async(dispatch_get_main_queue(), ^{
//Here you returns to main thread.
[[NSNotificationCenter defaultCenter] postNotificationName:#"DOWNLOAD_COMPLETE"
object:nil];
});
});
This will change the title of self.someButton to whatever you want, as in this case as Already Downloaded.
Hope that helps.
I loaded a UIView from a UIViewController. This UIView contains a (big) UICollectionView.
The transition from the first UIView to the second UIView is very slow: It seems that when the rendering of all collection's cells is done the second view can show up.
In the second UIView, I tried.
- (void)viewDidAppear:(BOOL)animated
{
[activityView stopAnimating];
NSLog(#"did appear %#",[NSDate date]);
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[activityView startAnimating];
NSLog(#"will appear %#",[NSDate date]);
}
In the NSLog, there is no time difference between the two events, and in fact the second UIView shows up in about 1 second after the event viewDidAppear.
At this point, I would start a UIActivityIndicator, as in the code. But the indicator is never shown.
Any hint?
Your problem here is that you're probably blocking the main thread by maybe doing some disk IO or network activity or heavy computations, and that is why you're experiencing this delay.
I'd recommend that you do all this on a secondary thread while showing a UIActivityIndicator. On the completion you can then hide the activity indicator and show the collection view.
EDIT:
N.B. There is probably a better way to go, but i'm not very familiar with collection views.
A really easy fix would be to keep a BOOL ivar in the view controller where you load the collection view. Call it shouldLoadData and set it to NO in your viewDidLoad method. Then all you need to do is to return 0 to your UICollectionViewDelegate methods numberOfSectionsInCollectionView: and collectionView:numberOfItemsInSection:.
Finally in your viewDidAppear method, you set shouldLoadData to YES and call reloadData on your collectionView. The tricky part at this point is to figure out a way to tell when the collection view finished reloading its data so that you can stop the activity indicator.
I found out that it is not even that tricky, reloadData just queues up on the main thread, so you can just queue another task on the main thread after you make the call to reloadData. Just do:
[self.collectionView reloadData];
dispatch_async(dispatch_get_main_queue(), ^{
[self.activity stopAnimating];
});
And you'll get the desired behaviour. You should be aware, however, that this would still block the main thread.
E.g. if you have a back button, it could not be pressed until the data is fully loaded (it could actually be pressed, but it would not have any visible effect until then).
I'm using AVPlayer's -addBoundaryTimeOserverForTimes:queue:usingBlock: to execute some code at a specific time in my video (in this case, I want a un-hide a button when my video reaches its duration. Code is as follows:
- (void)viewWillAppear:(BOOL)animated
{
...
_player = [AVPlayer playerWithURL:videoURL];
AVPlayerLayer *newPlayerLayer = [AVPlayerLayer playerLayerWithPlayer:_player];
[newPlayerLayer setFrame:_videoView.bounds];
[_videoView.layer addSublayer:newPlayerLayer];
_observer = [_player addBoundaryTimeObserverForTimes:#[[NSValue valueWithCMTime:_player.currentItem.duration]] queue:NULL usingBlock:^{
[someButton setHidden:NO];
}];
...
}
For whatever reason, sometimes the block of code fires and the button becomes visible, and sometimes it doesn't. Haven't been able to find a pattern in this behavior. It happens very often (almost always) in the simulator, and occasionally when on a device. Has anyone encountered this problem? Any ideas what might be going on?
Edit
Also, if I put a breakpoint on the block, it ALWAYS fires.
Main queue sometimes not call.
You can use Sub queue, and call Main queue in Sub-queue's block.
// dispatch queue setting
dispatch_queue_t subQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
// notification setting
__block id blockObserver;
blockObserver = [self.queuePlayer addBoundaryTimeObserverForTimes:boundary
queue:subQueue // if NULL use mainQueue
usingBlock:^{
// do something
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_async(mainQueue, ^{
// do something
});
}];
For those wanting to observe when the player ends:
I browse this question about once a year because I always forget about the fix that works for me. This time around I had this issue on macOS. I am seeing the same behavior, the observer block sometimes does not get called. When I switch back from the app that is being debugged to Xcode the block suddenly fires. This might be related to having a breakpoint set in the block as described by the OP.
Here's the fix however: Simply switch to AVPlayerItemDidPlayToEndTimeNotification as described in this answer. Note however, as the name implies the notification's object is the player's current item not the player itself!
Because this notification triggers at the end time of an item, instead of observing some "boundary time" simply set the item's forwardPlaybackEndTime if you need another time than the item's actual end time, i.e. duration.