working on parent UI from containerview (iOS - objective-c) - ios

I have a method in my main viewcontroller:
- (void)playMusic:(NSString*)songTitle :(NSString*)songArtist :(NSString*)songDuration :(NSString*)songUrl{
[songPlayer pause];
songPlayer = [[AVPlayer alloc]initWithURL:[NSURL URLWithString:songUrl]];
[songPlayer play];
self.songSlider.maximumValue = 100;
self.songSlider.value = 0;
}
I call it from a view inside a containerview like this (from a button click)
[mainController playMusic:songTitle:songArtist :songDuration :songUrl];
songPlayer (it s an AVPlayer) is doing his job well, the song start without problem.
But the songSlider (it s an UISlider) is not updated.
If I call the exam same instructions:
self.songSlider.maximumValue = 100;
self.songSlider.value = 0;
inside viewDidLoad method of my main cotroller, songSlider is updated without problem.
That makes me think the problem is that I call the function from the containerview? in this case how can I fix this? Thank you

I answered a similar question about an hour ago.
I suspect the problem to be that your button click action is not being called on the main thread. I didn't find any resources to indicate if UIControl always fires events on the main thread and thus there is a possibility that your code is running on a background thread. This is important because you cannot make UI modifications on any other thread than the main thread.
Try the following in your button callback
dispatch_sync(dispatch_get_main_queue(), ^{
[mainController playMusic:songTitle:songArtist :songDuration :songUrl];
});

Related

How to kill multiple threads in objective-c

I have created a UIButton and on click event, I am showing an image in the web view. Also, I am refreshing the image in every 30 sec. But when I click on button multiple times, refresh method get called multiple time as well.
I want it to work like, It saves last click time and refreshes as per that time instead of multiple times.
What can I do for it?
I tried to kill all previous thread instead of the current thread but that's not working.
Please help if anyone already know the answer.
Below is my image refresh code:
- (void)refreshBanner:(id)obj {
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
if (![SNRunTimeConfiguration sharedInstance].isInternetConnected) {
[self removeBannerAdWithAdState:kADViewStateNotConnectedToInternet];
return;
}
if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground) {
self.bannerPaused = YES;
return;
}
self.adView.hidden = YES;
UIViewController *topController = [UIApplication sharedApplication].keyWindow.rootViewController;
topController = [SNADBannerView topViewControllerWithRootViewController:topController];
if ([self checkInViewHierarchy:self parentView:topController.view]) {
// NSLog(#"Visible View Is: %#", self.adId);
SNADMeta *meta = [[SNADDataBaseManager singletonInstance] adToShowWithBanner:YES excludeTyrooAd:YES audio:NO zoneId:self.adSoptZoneId fixedView:NO condition:nil contextualKeyword:nil onlyFromAJ:NO];
SNADAdLocationType type = SNADAdLocationTypeHeader;
if (self.bannerType == SmallViewTypeFooter) {
type = SNADAdLocationTypeFooter;
}
if (self.isFromCustomEvent) {
type = SNADAdLocationTypeAdMobBanner;
}
NSString *message = meta ? nil : kSNADOppMissReason_NoAdToShow;
[SNRunTimeConfiguration fireOpportunityForAdLocation:type zoneId:self.adSoptZoneId reason:message];
NSLog(#"******************* Opportuninty fired for refresh banner ***************************");
if (meta) {
self.meta = meta;
[self updateContentForWebAd:nil];
[self updateStatsForAd];
//fireImpression
[SNADBannerView fireImpression:self.meta];
if ([meta.adSource isEqualToString:kSNADParameter_APC]) {
self.sdkMediation = [[SdkMediation alloc] init];
[self.sdkMediation fireTrackingAdType:self.meta.type isFill:YES];
}
// Ad Height Delegate.
if ([self.meta.displayType isEqualToString:kSNADDisplayType_web]) {
self.adHeightDelegateCalled = YES;
NSInteger height = self.meta.height.integerValue;
self.bannerCH.constant = height;
if ([self.callBackDelegate respondsToSelector:#selector(adWillPresentWithHeight:adId:adType:)]) {
[self.callBackDelegate adWillPresentWithHeight:height adId:self.adId adType:SeventynineAdTypeMainStream];
}
}
} else {
[self removeBannerAdWithAdState:kADViewStateNoAdToShow];
if ([meta.adSource isEqualToString:kSNADParameter_APC]) {
[self.sdkMediation fireTrackingAdType:self.meta.type isFill:NO];
}
return;
}
} else {
// NSLog(#"View Which Is Not Visible Now: %#", self.adId);
}
SNAdConfiguration *configuration = [SNAdConfiguration sharedInstance];
[self.timer invalidate];
self.timer = [NSTimer scheduledTimerWithTimeInterval:configuration.autoRefRate target:self selector:#selector(refreshBanner:) userInfo:nil repeats:NO];
}];
}
Use GCD, and not NSOperationQueue.
Then you step away from your immediate task. You do lots and lots of complicated things inside refreshBanner. And you will do more complicated things to make it work when the user taps multiple times.
Think about what exactly you need. Abstract the "refresh automatically, and when the button is clicked, but not too often" into a class. Then you create a class that takes a dispatch_block_t as an action, where a caller can trigger a refresh anytime they want, and the class takes care of doing it not too often. Then you create an instance of the class, set all the needed refresh actions as its action block, refreshBanner just triggers a refresh, and that class takes care of the details.
You do that once. When you've done it, you actually learned stuff and are a better programmer than before, and you can reuse it everywhere in your application, and in new applications that are coming.
NSOperationQueue have cancelAllOperations method. But for the main queue it's not a good decision to use this method, cause main queue is shared between different application components. You can accidentally cancel some iOS/other library operation together with your own.
So you can create NSOperation instances and store them in an array. Then you can call cancel for all scheduled operations by iterating trough this array, and it will only affect your operations.
Note that block operations doesn't support cancellation. You will need to create your own NSOperation subclass, extract code from your execution block into that subclass main method. Also, you'll need to add [self isCancelled] checks that will abort your logic execution at some points.
I forgot to mention that currently your execution block is fully performed on the main queue. So, you'll need to move any heavy-lifting to background thread if you want to cancel your operation in the middle of processing from main thread.
I need to add that I agree with #gnasher729 - this doesn't look like an optimal solution for the problem.
I have resolved the issue.
Multiple threads created because a new view is created every time I call the API to display image. So now I am removing views if any available before displaying image, then only last object remains and refresh is called as per last called time.
Every View has it's own object that's why multiple threads has created.
By removing views my issue has been resolved.
Thanks everyone for replying.

Activity indicator not working in iOS?

I've to play A video from server and it takes some time. So I've Written an activityIndicator and started animating it. but the activity indicator is not showing up until the video is playing. Firs I've hidden activity indicator later unhidden it and started animating but its shown when the video is started playing.
Here is my code:
for info indicatorView is a UIView and I've indView is an UIIndicatorView inside of UIView; And I'm using AV Player for playing;
Please Read Clearly that indicator view is showing up in the view, but the problem is not showing up when i Said start animating but after some time when video is started.
indicatorView.hidden = NO;
[indView startAnimating];
//Here Indicator view must shown in VIEW but it's not showing up;
MediaItem *item = [allVideos objectAtIndex:indexPath.row];
[playerModel playMyVideo:item];
// now here after 5 sec when video is ready to play it's showing up
What is the problem.
I've used dispach asynch in both the places but no use
Just add one method like,
-(void) videoPrepareToPlay {
MediaItem *item = [allVideos objectAtIndex:indexPath.row];
[playerModel playMyVideo:item];
}
and call that method like,
indicatorView.hidden = NO;
[indView startAnimating];
[self performSelector:#selector(videoPrepareToPlay) withObject:nil afterDelay:0.2];
It may be work for you..
I've used dispach asynch in both the places but no use
But is this code being run on the main thread? UI updates should be executed on the main thread.
dispatch_async(dispatch_get_main_queue(), ^{
//UI code
});
Change [playerModel playMyVideo:item]; to
[playerModel performSelector:#selector(playMyVideo:) withObject:item afterDelay:0.0001f inModes:#[NSRunLoopCommonModes]];
Execute the ActivityIndicator code using performSelectorOnMainThread and retrieve the video using background thread.
ELSE
Easy Fix:
Use this https://github.com/jdg/MBProgressHUD

Move to next UIViewcontroller, while data to be displayed there, is still loading

I am writing an ios app, that has multiple UIViewcontrollers. They all have UITableViews that are filled with data, that is acquired from different API's. But the problem that I am facing is that, when I tap on a cell, the the app won't navigate to the next page, until the data for that page is acquired. This make the app look, mighty slow. I need some way to navigate to next page, where I can put some spinner animation to let the user know that it is acquiring data(atleast). I don't want the user to think that the app has crashed, or something(it stays in the same page for solid 7-10 seconds)
Thanks in advance
you should call all the api in the background so it wont effect the main thread, use the queue for call api in background queue like below.
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_async(queue, ^{
//call apis here
});
My solution to this problem is,You can call the API using dispatch queue,So that it will not affect other functionalities.
Please follow these simple steps
ViewController1 = VC1
ViewController1 = VC2
in VC1
[self.navigationController pushViewController:VC2 animated:YES];
in VC2
#implementation {
BOOL hasData;
}
-(void)viewDidLoad {
hasData = NO;
[self getAndShowData];
}
-(void)getAndShowData {
// Start Showing Spinner
// load your data from server and
// on success
1.) hasData = YES;
2.) Call reload tableview
// Remove spinner
}
- (NSInteger)numberOfRowsInSection:(NSInteger)section {
if(hasData == NO) return 0;
return actual number of rows.
}
}

AVPlayer boundary time observer fails to fire occasionally

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.

waiting in the fixed place in the code for a touch on the screen

For example in a thread (because I can't wait in main loop) I have this code :
-(void) game {
for (Players player in players) {
if (player.type == IA) { // computer plays
answer = [player play];
else {
[ui showQuestion]; // user plays with the touch screen
// here waiting for the answer touch
answer = ???????????????? // return from waiting after touch callback
}
[answersArray addObject:answer];
}
answer = [self bestAnswer : answersArray];
[ui showTheBestAnswer : answer];
}
Is there a solution to wait for an UI Event in a fixed code place ?
without blocking the main loop of course.
Thank you very much for your help,
jpdms
First of all, I highly recommend that you read Apple's Concurrency Programming Guide, included with the Xcode documentation. In general, there are much better alternatives to threads, especially in the example you provide.
However:
If the game method is executing on a separate thread, then the proper way to signal the thread is using NSCondition. Create an instance and make sure both the code above and the touch handler has access to it.
NSCondition *playerDidTouchCondition = [[NSCondition alloc] init];
In the game method, you wait on the condition like this:
[ui showQuestion];
[playerDidTouchCondition lock];
[playerDidTouchCondition wait];
[playerDidTouchCondition unlock];
// do something with answer
Your game thread will sleep until the condition has been signaled. In your touch handler you would do this:
answer = whatever the user did
[playerDidTouchCondition lock];
[playerDidTouchCondition signal]; // wake up one of the sleeping threads
[playerDidTouchCondition unlock];
The example code you have above really doesn't demonstrate the need for a separate thread, however. You could very easily store a currentPlayerIndex somewhere and proceed to the next player inside of the button handler for the answer button.
Also, you MUST ensure that any UI updates are actually happening on the main thread. I hope that your lines like [ui showQuestion] are queuing calls on the main thread. In Cocoa you can do this easily with something like: [ui performSelectorOnMainThread:#selector(showQuestion)];
You really, really, really should not be using a separate thread for this.

Resources