I have a problem using the -(void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccesoryControlTapped:(UIControl *)control method. I tried many ways to test it but it just won't appear. Can anyone find a mistake
-(MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)anno{
MKPinAnnotationView *retval=nil;
if(retval ==nil){
retval=[[[MKPinAnnotationView alloc]initWithAnnotation:anno reuseIdentifier:#"Annotations"]autorelease];
UIButton *disclosure=[UIButton buttonWithType:UIButtonTypeDetailDisclosure];
retval.rightCalloutAccessoryView=disclosure;
[retval setPinColor:MKPinAnnotationColorGreen];
retval.animatesDrop=YES;
retval.canShowCallout=YES;
}
return retval;
}
-(void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccesoryControlTapped:(UIControl *)control{
NSLog(#"Tap");
PhotoDetail *detail=[[PhotoDetail alloc]initWithNibName:#"PhotoDetail" bundle:nil];
UINavigationController *nav=[[UINavigationController alloc]initWithRootViewController:detail];
[self presentModalViewController:nav animated:YES];
}
Make sure you implemented the
MKMapViewDelegate protocol
in the parent ViewController.
Related
- (void)sendSmsWithSubject:(NSString *)subject andBody:(NSString *)body {
MFMessageComposeViewController *smsCompose = [[MFMessageComposeViewController alloc] init];
smsCompose.subject = subject;
smsCompose.body = body;
smsCompose.messageComposeDelegate = self;
[self presentViewController:smsCompose animated:YES completion:nil];
}
As you can see on the attached screenshot, the first table cell "TestA" is covered by the text field on MFMessageComposeViewController. Above are the code snippet.
It seems like a bug on MFMessageComposeViewController, however, after doing some searches on stackoverflow, etc, there is no record for that "issue". Is there anything wrong or missed on my code? Thanks in advance for your help.
Notes: This UI issue is not happen on iOS10 devices.
Finally figured out that, in my case, the issue was caused by:
[[UIScrollView appearance] setContentInsetAdjustmentBehavior:UIScrollViewContentInsetAdjustmentNever];
As fix/workaround:
- (void)sendSmsWithSubject:(NSString *)subject andBody:(NSString *)body {
[[UIScrollView appearance] setContentInsetAdjustmentBehavior:UIScrollViewContentInsetAdjustmentAutomatic];
MFMessageComposeViewController *smsCompose = [[MFMessageComposeViewController alloc] init];
smsCompose.subject = subject;
smsCompose.body = body;
smsCompose.messageComposeDelegate = self;
[self presentViewController:smsCompose animated:YES completion:nil];
}
- (void)messageComposeViewController:(MFMessageComposeViewController *)controller didFinishWithResult:(MessageComposeResult)result {
// Disable contentInsetAdjustmentBehavior when leave MFMessageComposeViewController.
[[UIScrollView appearance] setContentInsetAdjustmentBehavior:UIScrollViewContentInsetAdjustmentNever];
[self dismissViewControllerAnimated:YES completion:nil];
}
Hope this is helpful to someone else who meet the same issue. Thanks.
DemoViewController is responsible for showing tutorial to the user. Contains animations and timer to repeat gesture demo while ignored by the user. Is instantiated form DataViewController. Is nil-ed, but later respawns on its internal timer. I need it to be completely gone so it is not created again when user returns to the first page.
dataViewController.h
#import "DemoViewController.h"
#property (strong,nonatomic) DemoViewController *demoController;
dataViewController.h
-(void) viewWillAppear:(BOOL)animated {
// demoPageNumber is 0
if ((self.demoController== nil) && ([_pageNumber isEqualToNumber:demoPageNumber])){
self.demoController = [[DemoViewController alloc] initWithView:self.view];
}
}
-(void) viewWillDisappear:(BOOL)animated{
[self.demoController free]; // invalidate timer, nil all internal objects
self.demoController=nil; // should free object
}
DemoViewController.m
-(void) free{
[animationRespawnTimer invalidate];
animationRespawnTimer=nil;
}
-(void) respawnDemoWithSelector:(SEL)selector{
NSLog(#"Timer fired %#", self);
[self resetTimer];
animationRespawnTimer = [NSTimer scheduledTimerWithTimeInterval:10
target:self
selector:selector
userInfo:nil
repeats:NO];
}
-(void) showScrollDemo{
NSLog(#"showScrollDemo fired");
[self stopPreviousAnimations];
scrollHandView.frame = CGRectMake(315.0, 700.0, 100, 100);
scrollHandView.hidden=NO;
scrollHandView.alpha=1;
[UIView animateWithDuration:3.0
delay: 0.0
options: (UIViewAnimationOptionCurveEaseOut |
UIViewAnimationOptionRepeat )
animations:^{
[UIView setAnimationRepeatCount:3];
scrollHandView.frame = CGRectMake(315.0, 300.0, 100, 100);
}
completion:^(BOOL finished){
[UIView animateWithDuration:1.0 delay:0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
scrollHandView.alpha=0;
}
completion:^(BOOL finished){
scrollHandView.hidden=YES;
[self respawnDemoWithSelector: #selector(showScrollDemo)];
}
];
}
];
}
When the page is loaded and if this is the first page, demoController is instantiated, and on exit from the page nil-ed after clean-up (custom free method). According to my understanding this should delete the demoController object with all its contents including the timer. And debug area shows exactly that! Until when on the new page the demoController timer mystically respawns from nowhere with previous object ID.
17:59:14.041 viewWillAppear begin (self.demoController null)
18:00:05.346 viewWillAppear, <DemoViewController: 0x7580310> //demoController created
18:00:15.786 in the demoController method the "showScrollDemo" is fired
18:00:19.834 viewWillAppear end <DemoViewController: 0x7580310>
Page was loaded, demo performed fine. Now I'm flipping the page. viewWillDisappear event is fired.
18:01:17.966 viewWillDisappear begin, send "free" message to demoController
18:01:17.966 "free" was performed from <DemoViewController: 0x7580310>
18:01:34.059 viewWillDisappear end (self.demoController null)
So, the "self.demoController" is null. Then the demoController respawns itself with previous ID
18:02:36.514 Timer fired <DemoViewController: 0x7580310>
Why? Timer can not respawn, it is set to repeats:NO.
I assume that it is the completion block of the animation that calls respawnDemoWithSelector and creates a new timer.
According to this answer: https://stackoverflow.com/a/9676508/1187415, you can stop all running
animations with
[self.view.layer removeAllAnimations];
Alternatively, you can add a boolean property done to the DemoViewController which is set
to YES in the free method, and checked in the completion block of the animation:
if (!self.done)
[self respawnDemoWithSelector: #selector(showScrollDemo)];
UPDATE: The animation blocks capture a strong reference to self, thus preventing
the object from being deallocated. The standard solution to that "retain-cycle" problem
(assuming that you use ARC) is to use a weak reference to self. That would look like this:
__weak typeof(self) weakSelf = self;
[UIView animateWithDuration:3.0
delay: 0.0
options: (UIViewAnimationOptionCurveEaseOut |
UIViewAnimationOptionRepeat )
animations:^{
[UIView setAnimationRepeatCount:3];
weakSelf.scrollHandView.frame = CGRectMake(315.0, 300.0, 100, 100);
}
completion:^(BOOL finished){
[UIView animateWithDuration:1.0 delay:0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
weakSelf.scrollHandView.alpha=0;
}
completion:^(BOOL finished){
weakSelf.scrollHandView.hidden=YES;
[weakSelf respawnDemoWithSelector: #selector(showScrollDemo)];
}
];
}
];
weakSelf does not hold a strong reference to the DemoViewController and is set to nil
automatically if the object it points to is deallocated. In that case, all message sent to weakSelf inside the blocks do just nothing.
If it matters:
- I am using storyboards
- Core data
- xcode 4.6
My app has a UITableViewController for a specific view. On that view, if the user clicks a button the software goes through a few processes where data is downloaded from an Internet API and saved into core data. I'm confident this is a resource hog, which is why I am attempting to complete those processes in separate threads.
Note:
- There is an order of operations because legs are dependent on exchanges. Exchanges are dependent on the race and positions. Positions are dependent on the race. Otherwise I would have executed everything asynchronously.
Issues:
- This is my first time working with Grand Central Dispatch. I'm not sure I am doing it correctly.
- If I comment out the data processing the UIProgressView is visible and updates as expected. With the data processing in place the system seems too bogged down to even display the UIProgressView.
The methods managing the downloads and progress is below.
- (IBAction)downloadNow:(id)sender {
[progressView setHidden:NO];
[progressView setProgress:0.1 animated:YES];
dispatch_sync(backgroundQueue, ^(void){
[self saveRace];
[self updateProgress:0.2];
});
dispatch_sync(backgroundQueue, ^(void){
[self savePositions];
[self updateProgress:0.3];
});
dispatch_sync(backgroundQueue, ^(void){
[self downloadExchanges];
[self saveExchanges];
[self updateProgress:0.4];
});
dispatch_sync(backgroundQueue, ^(void){
[self downloadLegs];
[self saveLegs];
[self updateProgress:0.5];
});
dispatch_sync(backgroundQueue, ^(void){
Utilities *utilities = [[Utilities alloc] init];
[utilities calculateStartTimes:race with:managedObjectContext];
[self updateProgress:1.0];
});
}
(void)updateProgress:(double)completedPercentage {
if (completedPercentage == 1.0) {
[self goHome];
} else if ([importExchanges count] > 0) {
[progressView setProgress:completedPercentage animated:YES];
}
}
Any help is greatly appreciated.
The method dispatch_sync will block the calling thread, which I believe in your case is the main thread. So I think it is better to wrap those dispatch_sync blocks into one dispatch_async block.
As an example:
dispatch_async(backgroundQueue, ^(void){
[self saveRace];
[self updateProgress:0.2];
[self savePositions];
[self updateProgress:0.3];
[self downloadExchanges];
[self saveExchanges];
[self updateProgress:0.4];
[self downloadLegs];
[self saveLegs];
[self updateProgress:0.5];
Utilities *utilities = [[Utilities alloc] init];
[utilities calculateStartTimes:race with:managedObjectContext];
[self updateProgress:1.0];
});
After that you can wrap the updating of the progressView to be done in main thread because it is a UI update.
- (void)updateProgress:(double)completedPercentage {
if (completedPercentage == 1.0) {
[self goHome];
} else if ([importExchanges count] > 0) {
dispatch_async(dispatch_get_main_queue(), ^{
[progressView setProgress:completedPercentage animated:YES];
});
}
}
I am having a hell of a time with AVQueuePlayer. Very simply, I am making it on an array of AVPlayerItems built with playerItemWithURL: and pointing to video assets on a server out on the Net.
If I try to run this thing in the stimulator (sic) it plays through the first asset, begins playing the second and just dies. Sometimes it doesn't even get through the first asset.
Now, I understand that the sim behaves differently than the device, so I tried it on the device as well, with the same problem.
When I log the various status changes on the item, I see that the buffer runs out, and the player just dies. This seems a bit silly to me for a construct that is playing a queue of items... of course the buffer may underrun, but why would it just die?
Am I doing something wrong, or is this how it is supposed to behave?
I have gotten around this problem by observing the loadedTimeRanges property and when it changes sending PLAY to the player. But this results in stuttering playback, which sucks too.
Here is my code. Can someone please tell me how to do this without the playback being complete crap?
- (void)viewDidLoad {
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(handlePlayerItemReachedEnd:) name:AVPlayerItemDidPlayToEndTimeNotification object:self.queuePlayer];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(handlePlayerStalled:) name:AVPlayerItemPlaybackStalledNotification object:self.queuePlayer];
NSString *baseURL = #"http://www.aDomain.com/assets/";
NSMutableArray *vidItemArray = [[NSMutableArray alloc] initWithCapacity:5];
for (int i = 1; i <= 5; i++) {
AVPlayerItem *vid = [AVPlayerItem playerItemWithURL:[NSURL URLWithString:[baseURL stringByAppendingFormat:#"%d.mov", i]]];
[vid addObserver:self forKeyPath:#"status" options:0 context:nil];
[vid addObserver:self forKeyPath:#"playbackBufferEmpty" options:0 context:nil];
[vid addObserver:self forKeyPath:#"loadedTimeRanges" options:0 context:nil];
[vidItemArray addObject:vid];
}
self.queuePlayer = [AVQueuePlayer queuePlayerWithItems:vidItemArray];
[self.mPlaybackView setPlayer:self.queuePlayer];
}
- (void)handlePlayerItemReachedEnd:(NSNotification *)notification {
NSLog(#"Clip #%d finished playing", [self.queuePlayer.items indexOfObject:self.queuePlayer.currentItem]);
}
- (void)handlePlayerStalled:(NSNotification *)notification {
NSLog(#"CLIP STALLED...");
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if ([object isKindOfClass:[AVPlayerItem class]]) {
AVPlayerItem *item = (AVPlayerItem *)object;
if ([keyPath isEqualToString:#"status"]) {
switch (item.status) {
case AVPlayerItemStatusFailed:
NSLog(#"player item status failed");
break;
case AVPlayerItemStatusReadyToPlay:
NSLog(#"player item status is ready to play");
[self.queuePlayer play];
break;
case AVPlayerItemStatusUnknown:
NSLog(#"player item status is unknown");
break;
}
}
else if ([keyPath isEqualToString:#"playbackBufferEmpty"]) {
if (item.playbackBufferEmpty) {
NSLog(#"player item playback buffer is empty");
}
}
else if ([keyPath isEqualToString:#"loadedTimeRanges"]) {
NSLog(#"Loaded time range = %#", item.loadedTimeRanges);
self.queuePlayer play];
}
}
}
You should not add many AVPlayerItem to AVQueuePlayer initially because they will begin buffering at the same time, at discretion of the framework.
Attempt to load one file and then on KVO currentItem changed queue the next one, it might solve your problem. I'm using this to achieve gapless playback.
I am trying to pass a string between two view controllers in a storyboard, core data program. The way I have it set up I want the next view to be pushed only for one section only. Hence the use of "didSelectRowAtIndexPath" rather than "prepareForSegue". Below is my code for "didSelectRow…" "five" is the viewController class being pushed.
- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.section == 0)
{
five *five=[[five alloc]init];
five.delegate = self;
[self performSegueWithIdentifier:#"segue1" sender:self];
}
}
"two" is the parent view. Below is the method used in a protocol created in the "five" class. "friendString" is in the "two" class and "fiveString" in "five". When the view is popped, the strings should be the same and then I use the string added in "five" in a UITextField in "two". However it doesn't update when the view is popped.
- (void)popFive:(five *)controller
{
self.friendString=controller.fiveString;
[self update];
[self.tableView reloadData];
}
I believe the problem is related to how the seque is done in "didSelectRow…" Any help or ideas will be appreciated. Thanks.
I think when using "didSelectRow..." as a segue you also have to use "prepareForSegue". When I added this it seemed to be able to pass strings between the two view when the child view was popped. I included the code below
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.section == 0)
{
[self performSegueWithIdentifier:#"segue1" sender:self];
}
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"segue1"])
{
five *five = segue.destinationViewController;
five.delegate = self;
}
}