UPDATE: Tried the links you suggested, but can't get it to work.
Just to confirm I'm understanding it right, these are the steps I took.
First view controller is called FirstViewController, second is called SearchViewController.
Import SearchViewController into FirstViewController, and add
In FirstViewController.m, add the following:
SearchViewController *svc =[[SearchViewController alloc] init];
svc.delegate =self;
In SearchViewController.h I added:
#protocol SearchViewControllerDelegate
(void)setLat:(CLLocationDegrees)lat setLon:(CLLocationDegrees) lon;
as well as the delegate property:
#property (assign) id <SearchViewControllerDelegate> delegate;
In SearchViewController.m I synthesized delegate, and added
[self.delegate setLat:51.0000 setLon:-0.10000];
From some NSLog testing it seems that the method setLatSetLon method itself is never called.
I'm currently trying to finish off my university iPad application. The purpose of it is to provide a live google/apple maps view of the campus with overlays of annotations and MKPolygons. This all works fine.
I've now added a button that loads a popover (the second view controller) with a table of the campus buildings (which are themselves objects of CampusBuilding, where the coordinates are stored) as well as a search box, and this is all fine. What I'm looking to do is when the user selects a row, the map view centres on that specific building. I've written a method below which works fine when I reference it from the maps view controller itself.
But how would I do this from the second view controller?
Here's the method that is present in the first view controller:
- (void)setLat:(CLLocationDegrees)lat setLon:(CLLocationDegrees) lon{
MKCoordinateSpan span;
span.latitudeDelta = (double) .003;
span.longitudeDelta = (double).003;
//Define the default region to focus on
MKCoordinateRegion region;
region.span=span;
region.center=CLLocationCoordinate2DMake
(lat,lon);
//set the default region to 'region'
[_mapView setRegion: region animated:YES];
[_mapView regionThatFits:region];
}
Now in the second view controller, it works fine as a table view controller. I've set it up so when the user selects a row it loads a new view. I also tried to initiate an object of the first view controller and then wrote this:
FirstViewController* firstVC;
[firstVC setLat:building.latValue setLon:building.lonValue];
It all compiles but no movement happens, I assume because I'm initiating a new object, rather than referring to the one that is currently active.
Is there a way to do this? Comments on how I can improve the code are always welcome, I'm relatively new to this.
Thanks
You were right that firstVC will be a new instance of FirstViewController. Not the previous one.
What you need is to use Objective-C's delegate pattern. It is commonly used in this scenario. Here are two examples from my previous SO answers that have sample code on how to implement.
how to resume timer when poping to view2
Calling a method from another class in Objective C
Related
In my chatting application, I have a ChatViewController.m that allows users to message with the QuickBlox framework.
When a user sends an image, a background upload begins and a UIProgressView displays the progress of the upload.
But what if the user backs out of that view during the upload, and returns in, say, 10 seconds while the upload is still happening. I want the UIProgressView to still be active and accurate based on that upload. But dismissing the ViewController doesn't allow me to do that.
Can someone suggest what I should be doing in this situation?
EDIT: This is how I present the ChatViewController.m, depending on the chat selected from the CollectionView:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
if([segue.destinationViewController isKindOfClass:ChatViewController.class]){
ChatViewController *destinationViewController = (ChatViewController *)segue.destinationViewController;
if(self.createdDialog != nil){
destinationViewController.dialog = self.createdDialog;
self.createdDialog = nil;
}else{
QBChatDialog *dialog = [ChatService shared].dialogs[((UICollectionViewCell *)sender).tag];
AppDelegate *appDelegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];
appDelegate.dialog = dialog;
}
}
}
EDIT 2: I have implemented the ViewController as a singleton in my didSelectItemAtIndexPath. But now, the app presents only a black screen.
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
QBChatDialog *dialog = [ChatService shared].dialogs[indexPath.row];
ChatViewController *chatView = [[ChatViewController alloc] init];
chatView.dialog = dialog;
[self presentViewController:chatView animated:YES completion:nil];
}
You should change the way you display your chat view controller. Don't use a segue.
Instead, set up your chat view controller as a singleton. Set up an IBAction or other code triggered by the user selecting an item in your collection view.
In that IBAction or didSelectItem code, fetch a reference to your singleton, configure it as needed, and present it modally yourself using presentViewController:animated:completion:
That way your view controller will preserve it's contents between views.
As the other poster said in a comment, you might pull the download logic out of your view controller and into a separate download manager class. It depends on whether you need the ability to do asynchronous downloads in places other than your chat view controller.
I'm assuming based on your question that you are creating the view controller each time that you are presenting it. Instead of re-allocating and creating a new view controller each time you present the messaging view, make only one view controller that you can call and present from any other view controller.
A couple possible ways to do this are:
Create a singleton that has the messaging view controller as a property
Add the messaging view controller as a property on your route view controller
Make the messaging view a singleton itself so only one gets created in the entire life of the application
Doing any of these will make sure that the view persists each time that it's dismissed.
If that still doesn't fix the problem, you may be resetting the view in viewDidLoad or viewDidAppear which I don't think is actually what you want to do.
I have a GPS app that uses Google Maps to handle location based events. The app handles all location events within the app and does not switch to Googles own Google Maps app.
The storyboards can be seen in the image below.
In the app I have a main map view (My Map View Controller as in the StoryBoard) that displays the users current location as well as a list of marked locations around the user on the map. This map also contains a button that will take the user to a list of their marked points (List of Points Table View Controller). Selecting any of the list points takes them to a detailed description of the point (Log a Point). And finally clicking "View on Map" button on this view takes them to the last view (Submit Point Map View Controller) where they can see this point zoomed in on another views map.
Both the map views (My Map View Controller AND Submit Point Map View Controller) use similar code as listed below. However, when I run run the code and I get to "Submit Point Map View Controller", this views viewDidLoad method is executed twice as I have noticed while stepping through the code. This causes 2 views to load, one right after the other. I can also see this happening in the emulator. On the emulator the first view that loads has a back button titled "Log a Point" as would be expected as this was the previous view in the stack. The next view that loads simply has "Back" for the back button - as can be seen on the images below.
This is not an issue on the emulator and I can navigate back to Log a Point view. But on my phone the app crashes when I try and navigate back to Log a Point view and gives error "nested push animation can result in corrupted navigation bar. Finishing up a navigation transition in an unexpected state. Navigation Bar subview tree might get corrupted."
I have checked and I am not segue-ing to this view twice or doing anything that I am not doing on the first map view. Does anyone know why this views viewDidLoad method could be called twice? I have read on SO that this error is thrown from List views but I am not coming from a list view in this case - even though there is a list view earlier in the process.
Below is my Submit Point Map View Controller .h and .m files (some code omitted for brevity)
SubmitPointMapViewController.h
#import <UIKit/UIKit.h>
#import <GoogleMaps/GoogleMaps.h>
#interface SubmitPointMapViewController : UIViewController <CLLocationManagerDelegate>
{
}
#property (nonatomic) CLLocationCoordinate2D *location;
#property double latitudes;
#property double longitudes;
#end
SubmitPointMapViewController.m
#import "SubmitPointMapViewController.h"
#import <GoogleMaps/GoogleMaps.h>
#import <Parse/Parse.h>
#interface SubmitPointMapViewController () <GMSMapViewDelegate>
#end
#implementation SubmitPointMapViewController
{
GMSMapView *mapView;
CLLocationManager *locationManager;
}
#synthesize latitudes;
#synthesize longitudes;
- (void)viewDidLoad
{
// This entire method called twice - one right after the other
mapView.delegate = self;
locationManager = [[CLLocationManager alloc] init];
GMSCameraPosition *camera = [GMSCameraPosition cameraWithLatitude: latitudes longitude: longitudes zoom:17];
mapView = [GMSMapView mapWithFrame:CGRectZero camera:camera];
mapView.myLocationEnabled = YES;
[mapView setMapType:kGMSTypeNormal];
self.view = mapView;
// Set the MyLocationButton and add the button to the MapView
mapView.settings.myLocationButton = YES;
// Setup Markers on the Map
[self setupMarkersOnMap];
}
#end
EDIT: Below is my Connections inspector on the Log a Point view, as well as the segue code when the View on map button is pushed on the same view
- (IBAction)viewOnMapButtonPreseed:(id)sender
{
[self performSegueWithIdentifier:#"SubmitPointmapViewSegue" sender:sender];
}
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"SubmitPointmapViewSegue"])
{
SubmitPointMapViewController *vc = [segue destinationViewController];
vc.latitudes = pointObject.latitude;
vc.longitudes = pointObject.longitude;
}
}
As suggested by #Simon Goldeen and #pbasdf above - the issue was that I was pushing 2 map view controllers onto the stack. There was and old segue that I was using previously for debugging that was causing the issue. I deleted all segues to this map view and instead segued to the map view as follows:
SubmitPointMapViewController *vc = [[SubmitPointMapViewController alloc] init];
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
vc = (SubmitPointMapViewController *)[storyboard instantiateViewControllerWithIdentifier:#"SubmitPointMapViewControllerStoryboardID"];
[[self navigationController] pushViewController:vc animated:YES];
Thought I would post my answer here in case anyone else had the same issue.
All credit to #Simon Goldeen and #pbasdf for helping me troubleshoot this issue.
In my app I've got a UINavigationController.
In some of the pages I got a MKMapView as the back view (allows changes to map view the overlay UIView UI, so I can't make it as a image).
In some combinations, It's possible to have 3 or so MKMapView alloc'd.
The problem is that each MKMapView takes nearly 60MB, so the memory jumps up to 180+ MB only from the maps. not to mention if the user opens more ViewControllers with map, he'll get a OutOfMemoryException.
All the maps have the same content, I tried moving a map from ViewController to another, but according to another post in StackOverflow, moving UIView from ViewController to another is against MVC, not to mention it removes it from the caller ViewController.
Basically since all the maps contains the same content, and I want them the contain all the annotations it'd be best to just move the map but I had hard time with that.
I've got a solution out of the box which is to remove the MKMapView from its superView and on viewWillAppear realloc it, but this doesn't seem like the best idea.
I don't see any problem with passing a pointer to a single map view when you move from controller to controller. Unless you're viewing more than one map at a time, I don't see why there should be more that one instance. Remove the view in viewWillDisappear, and pass a pointer to the map in prepareForSegue:
- (void)viewDidLoad {
[super viewDidLoad];
if (! self.mapView) {
self.mapView = [[MKMapView alloc] initWithFrame:self.view.frame];
}
}
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self.view addSubview:self.mapView];
[self.view sendSubviewToBack:self.mapView];
}
-(void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[self.mapView removeFromSuperview];
}
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
SecondViewController *secVC = segue.destinationViewController;
secVC.mapView = self.mapView;
}
Subsequent controllers could have the same code in viewWillAppear, viewWillDisappear, and prepareForSegue (except for what controller is the destinationViewController). The map only needs to be instantiated once in the viewDidLoad method of the first controller. An even simpler implementation would be to have all the subsequent view controllers inherit from the first controller. If you do it that way, you won't need to put anything in viewWillAppear or viewWillDisappear. You only need to override prepareForSegue.
I have a ViewController with a TableView and a MapView and a button which opens a PopOver with another TableView. There the user can select some positions which should be shown on the Table and the MapView in the mother ViewController.
I have a function which calculates the table data and draws those annotations in the mother ViewCoontroller. This is called from the child ViewController by initiating it:
MotherViewController * MVC = [[MotherViewController alloc] init];
[MVC calculateresults];
The selected data is then passed back via protocol and the function calculates the proper results. However the table won't reload because [self.TableView reloaddata] doesnt work when the PopOver is still opened. If I close the PopOver and start the function directly on the mother ViewController everything works fine.
Tried already this, but doesn't work:
iPad SplitViewController: Reloading the root view controller's tableview from the detail view controller
How can i update a Map and a Table in a mother ViewController while a PopOver is opened?
The reason this is failing is not that "[self.TableView reloaddata] doesnt work when the PopOver is still opened." The reason is that self is a different self. You are saying:
MotherViewController * MVC = [[MotherViewController alloc] init];
[MVC calculateresults];
So MVC is not the MotherViewController whose view you see behind the popover. It is a different MotherViewController - the one you just created with alloc. So the MotherViewController that you see behind the popover never gets any message, so it does nothing.
Naturally when you do this directly in that MotherViewController, it works.
Instead of saying
MotherViewController * MVC = [[MotherViewController alloc] init];
You need to arrange for your popover to have a reference to the already existing MotherViewController.
I swear I saw something about this in the documentation but can't find it (as I'm not sure what to look for).
Basically in my app, the first VC consists of a map where users can select annotations and a callout button on each one. Otherwise, the user can also select a button that'll open a searchable table of the buildings on the map and load view controllers displaying the details (basically a master-detail setup).
What I want to do is for the callout to load a specified path for that particular building. So if I tap the callout on building A, it opens the detail view controller to show the information.
How do I do this, or what piece of documentation will help?
From reading this, it sounds like the map view an the table view belong to the same view controller. If so, then the view controller is where to want to centralize things. Again assuming the model is an array of data objects, have a method in the controller that does all the work of coordinating the different views.
- (void)selectModelAtIndex:(NSUInteger)index sender:(id)sender
{
if (self.isSelectingModel)
return;
self.isSelectingModel = YES; // Stops recursive calls to -selectModelAtIndex:
NSIndexPath *indexPath = [self indexPathForModelAtIndex:index];
[self.tableView selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionNone;
[self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionNon animate:NO];
MKAnnotation *annotation = [self annotationForModelAtIndex:index];
[self.mapView selectAnnotation:av.annotation animated:NO];
[self performSegueWithIdentifier:#"Identifier Of Detail View" sender:sender];
self.isSelectingModel = NO;
}
This is a single method which keeps the tableView and the mapView in sync before segueing to the details view controller. You need to have a isSelectingModel property, -indexPathForModelAtIndex:, and -annotationForModelAtIndex:.
This method is called by the table view and map view did select callbacks.