I´m currently trying to pass the city name retrieved by CLGeocoder successfully to a UILabel of another class. First the CLGecoder-class
FindLocation.h
#property (nonatomic, strong) NSString *cityName;
FindLocation.m - inside method (void)locationManager:(CLLocationManager *)manager...
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
if (self.isFirstUpdate) {
self.isFirstUpdate = NO;
return;
}
CLLocation *location = [locations lastObject];
if (location.horizontalAccuracy > 0) {
self.currentLocation = location;}
CLGeocoder *fgeo = [[CLGeocoder alloc] init];
// Reverse Geocode a CLLocation to a CLPlacemark
[fgeo reverseGeocodeLocation:location completionHandler:^(NSArray *placemarks, NSError
*error){
NSLog(#"%#", location);
// Make sure the geocoder did not produce an error
// before continuing
if(!error){
// Iterate through all of the placemarks returned
// and output them to the console
for(CLPlacemark *placemark in placemarks){
NSLog(#"%#",[placemark description]);
self.cityName = [placemark locality];
NSLog(#"city is %#",cityName); }
[self.locationManager stopUpdatingLocation];
[self.delegate findLocationDidGeocodeCityName:self.cityName];
} else {
// Our geocoder had an error, output a message
// to the console
NSLog(#"There was a reverse geocoding error\n%#",
[error localizedDescription]);
Further in my FirstViewController it looks like this:
FindLocation *cityname = [[FindLocation alloc] init];
[cityname cityName];
[self.cityLabel setText:(cityname.cityName)];
NSLog(#"CityName is...%#", cityname.cityName); //Log shows CityName is...(null)
I don´t know whats wrong here. Since the city is found there must be something wrong with the latter part of code, but I don´t know what. Is the passing of NSString - cityName implemented wrong?
I think the problem is that the geocode process is not instantaneous. You are instantiating FindLocation and then immediately trying to access cityName. However, even assuming that your init method triggers the location update, the geocoder will take a little bit of time to go to the server and get a response. Additionally, the location manager won't return a location immediately, and since it looks like that is what kicks off the geocoder, you have to wait a little bit longer for that as well.
One solution here would be to implement a delegate protocol on FindLocation so that it can notify FirstViewController when the geocoding has completed. Something like this:
FindLocation.h
#import <UIKit/UIKit.h>
#class FindLocation;
#protocol FindLocationDelegate <NSObject>
- (void)findLocationDidGeocodeCityName:(NSString *)cityName;
#end
#interface FindLocation : NSObject
#property (weak, nonatomic) id<FindLocationDelegate> delegate;
#end
and then in your CLGeocoder completionHandler after retrieving cityName:
[self.delegate findLocationDidGeocodeCityName:self.cityName];
Now, in FirstviewController.h, indicate that you conform to the FindLocationDelegate protocol:
#interface FirstViewController : UIViewController <FindLocationDelegate>
In your FirstViewController.m, set the delegate like so:
FindLocation *cityName = [[FindLocation alloc] init];
cityName.delegate = self;
and then also in FirstViewController, implement the delegate method:
#pragma mark - FindLocation Delegate
- (void)findLocationDidGeocodeCityName:(NSString *)cityName
{
[self.cityLabel setText:cityName];
}
There are other ways to do this as well, but the key is you have to wait until the completionHandler runs, and then notify any other interested objects about what got returned.
Related
I have an iOS 9.0 app that I am trying to add In-App Purchase to; (I have never used IAP before and found some code online that will hopefully get me started).
I decided to make the code reside in a Category for maintenance simplicity (I have only used Categories once before). That said, I'm having problems with the actual structure of the Category. This is the .h file's code in the category:
#import "SettingsViewController.h"
#import <StoreKit/StoreKit.h>
#interface SettingsViewController (Purchases)
#end
#define kInAppPurchaseManagerProductsFetchedNotification
#"kInAppPurchaseManagerProductsFetchedNotification"
#interface InAppPurchaseManager : NSObject <SKProductsRequestDelegate> {
SKProduct *proUpgradeProduct;
SKProductsRequest *productsRequest;
}
#end
This is the .m code:
#import "SettingsViewController+Purchases.h"
#implementation SettingsViewController (Purchases)
- (void)requestProUpgradeProductData {
NSSet *productIdentifiers = [NSSet setWithObject:#"com.runmonster.runmonsterfree.upgradetopro" ];
productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:productIdentifiers];
productsRequest.delegate = self;
[productsRequest start];
// we will release the request object in the delegate callback
}
#pragma mark -
#pragma mark SKProductsRequestDelegate methods
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
NSArray *products = response.products;
proUpgradeProduct = [products count] == 1 ? [[products firstObject] retain] : nil;
if (proUpgradeProduct)
{
NSLog(#"Product title: %#" , proUpgradeProduct.localizedTitle);
NSLog(#"Product description: %#" , proUpgradeProduct.localizedDescription);
NSLog(#"Product price: %#" , proUpgradeProduct.price);
NSLog(#"Product id: %#" , proUpgradeProduct.productIdentifier);
}
for (NSString *invalidProductId in response.invalidProductIdentifiers)
{
NSLog(#"Invalid product id: %#" , invalidProductId);
}
// finally release the reqest we alloc/init’ed in requestProUpgradeProductData
[productsRequest release];
[[NSNotificationCenter defaultCenter] postNotificationName:kInAppPurchaseManagerProductsFetchedNotification object:self userInfo:nil];
}
#end
The problem is the SKProduct and SKProductsRequest are not available to the .m file, which I'm sure is caused by the way I set up the .h file. Help would be greatly appreciated.
It looks like you're trying to use a category for an unsuitable purpose. In particular, it looks like your category is supposed to contain a property declaration. But, as the documentation says:
Categories can be used to declare either instance methods or class methods but are not usually suitable for declaring additional properties. It’s valid syntax to include a property declaration in a category interface, but it’s not possible to declare an additional instance variable in a category. This means the compiler won’t synthesize any instance variable, nor will it synthesize any property accessor methods. You can write your own accessor methods in the category implementation, but you won’t be able to keep track of a value for that property unless it’s already stored by the original class.
In Header file
#import <WatchKit/WatchKit.h>
#import <Foundation/Foundation.h>
#import <WatchConnectivity/WatchConnectivity.h>
#interface InterfaceController : WKInterfaceController<WCSessionDelegate>
- (IBAction)lastSongButtonClick;
- (IBAction)playSongButtonClick;
- (IBAction)nextSongButtonClick;
#property (strong, nonatomic) IBOutlet WKInterfaceLabel *songTitleLabel;
#property (strong, nonatomic) IBOutlet WKInterfaceButton *playSongButton;
#end
So I implemented the WCSessionDelegate and every time I receive about the UI, I would want it to update. So in my .m file I have:
- (void)session:(nonnull WCSession *)session didReceiveMessage:(nonnull NSDictionary<NSString *,id> *)message{
NSString* type = [message objectForKey:#"type"];
if([type isEqualToString:#"UIUpdateInfo"]){
NSLog(#"Watch receives UI update info");
[self handleUIUpdateInfo:[message objectForKey:#"content"]];
}
}
AND
- (void)handleUIUpdateInfo:(NSDictionary*)updateInfo{
[self.songTitleLabel setText:[updateInfo objectForKey:#"nowPlayingSongTitle"]];
[self.playSongButton setBackgroundImage:[updateInfo objectForKey:#"playButtonImage"]];
}
However, it doesn't seems to update. Is there any proper way to update?
You're halfway there. You've configured receiving the message on the watch side correctly, but you'll need to trigger a message to be sent when the UI is updated (therefore triggering didReceiveMessage to execute and update the appropriate content).
Where ever you are making changes to the UI, you'll need to include this:
NSDictionary *message = //dictionary of info you want to send
[[WCSession defaultSession] sendMessage:message
replyHandler:^(NSDictionary *reply) {
//handle reply didReceiveMessage here
}
errorHandler:^(NSError *error) {
//catch any errors here
}
];
Also, make sure you're activating the WCSession properly. This is usually done in viewDidLoad or willAppear depending on whether you're implementing this on the phone or the watch.
- (void)viewDidLoad {
[super viewDidLoad];
if ([WCSession isSupported]) {
WCSession *session = [WCSession defaultSession];
session.delegate = self;
[session activateSession];
}
}
You can see a full example of an end-to-end Watch to iPhone data transfer in this tutorial - http://www.kristinathai.com/watchos-2-tutorial-using-sendmessage-for-instantaneous-data-transfer-watch-connectivity-1
I have one model.
I want to update one attribute each 3 seconds after init, and I need to change the attribute in one other view controller later.
the code is:
MODEL
#interface Ap : NSObject
#property (nonatomic, retain) NSString *address;
#property (nonatomic, retain) NSString *sessionId;
+ (id)sharedInstance;
#end
#implementation Ap
#synthesize sessionId, address;
-(id) init {
if (self = [super init]) {
self.address = nil;
self.sessionId = nil;
[NSTimer scheduledTimerWithTimeInterval:3 target:self selector:#selector(heartbeat) userInfo:nil repeats:YES];
}
return self;
}
-(void) updateSession{
sessionId = #"some string";
}
- (void) update{
self.sessionId = #"some value from network";
}
+ (Ap *)sharedInstance {
if (!sharedInstance) {
sharedInstance = [[super allocWithZone:NULL] init];
}
return sharedInstance;
}
#end
CONTROLLER
- (void) viewDidLoad {
[super viewDidLoad];
[[Ap sharedInstance] updateSession];
}
The error is: (lldb) bad access when model update
And I change the updateSession method to
-(void) updateSession{
self.sessionId = #"some string";
}
The error is gone, can anyone tell me why?
You should use self.ap = [[Ap alloc] init]; in your app delegate. Your model object might be getting released when you try to call update on that which might cause a crash.
Your code will look like this,
#interface SomeAppDelegate : UIResponder{
#property(nonatomic, retain) Ap *ap; //declare property here
#end
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
if(!self.ap){
self.ap = [[Ap alloc] init]; //use property here as self.ap which will retain ap. Otherwise it will not retain it.
}
}
Update:
Looks like you have removed the previous question and added a completely new question to the previous one. Anyways I will try to answer this one as well.
The error is gone, can anyone tell me why?
The reason is same as what I mentioned above. If you use self.sessionId = #"some string";, you are retaining the object. Because it is a property and a property will internally retain since you have declared that property as retain. But if you use sessionId = #"some string";, its scope is only inside that method since you are not using the property. You are directly setting the value without calling property and it will autoreleased after that method. And hence you will get a bad access.
I would suggest you to go through the apple documentation to understand more about properties and its working.
I made an application to communicate with a REST API service. Now i need to send some gps coordinates to that service so i made another class in my application called MyCLController and i managed to get the coordinates of my position and also display them in a label in my app. What i am having trouble atm is this:
I don't know how to use the coordinates i get from the method :
-(void) locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
[self.delegate locationUpdate : [locations lastObject];
[CLLocationCoordinate2D coordinate = [[locations lastObject] coordinate];
double dblLagitude = coordinate.lagitude;
double dblLongitude = coordinate.longitude;
}
(which is implemented in my MyCLController class) into my viewcontroller where i developed the code to make a request to the server and send a string to the server. Anyone has any idea how i can declare these two variables so i am able to use them in my viewcontroller or what can i do ?
Thanks in advance
Create properties in MyCLController(in your .h file) as,
#property (nonatomic, assign) double dblLagitude;
#property (nonatomic, assign) double dblLongitude;
And your method should be modified as,
-(void) locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
[self.delegate locationUpdate : [locations lastObject];
[CLLocationCoordinate2D coordinate = [[locations lastObject] coordinate];
self.dblLagitude = coordinate.lagitude;
self.dblLongitude = coordinate.longitude;
}
You need to use the object of MyCLController in your viewcontroller and use the properties as myCLController.dblLagitude and myCLController.dblLongitude(assuming that the object of MyCLController is myCLController).
I am trying to pass the selected object in my coredata from the rootviewcontroller to the edit view. The selected object is being passed but is then becoming null after the theObject=selectedObject is being called. Anyone know what im doing wrong?
This is in the edit.h
#import <UIKit/UIKit.h>
#import <CoreData/CoreData.h>
#import "LearningAppDelegate.h"
#interface edit : UIViewController <UITextViewDelegate, UITableViewDelegate, UITableViewDataSource, UIActionSheetDelegate>{
UITableView *tableView;
NSManagedObject *theObject;
UITextView *messageView;
}
#property(nonatomic, retain) IBOutlet UITableView *tableView;
#property(nonatomic, retain) IBOutlet UITextView *messageView;
#property(nonatomic, retain) NSManagedObject *theObject;
-(id)initWithObject:(NSManagedObject *)selectedObject;
#end
This is in the edit.m:
-(id)initWithObject:(NSManagedObject *)selectedObject {
self = [super init];
if (nil == self) {
return nil;
}
NSLog(#"selectedObject: %#", selectedObject);
NSLog(#"selecetedObject.message: %#", [[selectedObject valueForKey:#"message"] description]);
theObject=selectedObject;
NSLog(#"theObject 1: %#", theObject);
NSLog(#"theObject.message 1: %#", [[theObject valueForKey:#"message"] description]);
return self;
}
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
NSLog(#"theObject 2: %#", theObject);
NSLog(#"theObject.message 2: %#", [[theObject valueForKey:#"message"] description]);
messageView.text=[[theObject valueForKey:#"message"] description];
[super viewDidLoad];
}
I am actually amazed that doesn't crash for you. You're assigning the variable selectedObject into the instance variable theObject without retaining it for your own use. By accessing the instance variable directly in the assignment 'theObject=selectedObject', you're bypassing the behavior granted by the #property declaration. This means that once selectedObject is finally dealloc'd, theObject will point to garbage memory.
The correct way to do this is to put theObject = [selectedObject retain]; in the -initWithObject: method and in -viewDidLoad to access it via self.theObject rather than just theObject.
In my own usage I prefer to give instance variables names different from the actual property name to avoid confusion. For example:
#interface SomeClass : NSObject
{
#private
NSManagedObject *_theObject;
}
#property (nonatomic, retain) NSManagedObject *theObject;
...
#end
#implementation SomeClass
#synthesize theObject = _theObject
...
- (void)dealloc
{
[_theObject release], _theObject = nil;
[super dealloc];
}
#end
I only have experience with Coredata on the desktop, but the problem looks like it would be with your initWithObject method. At no point do you actually insert the new object into the managed object context. You should be using this method to make new objects:
- (id)initWithEntity:(NSEntityDescription *)entity insertIntoManagedObjectContext:(NSManagedObjectContext *)context
As an example in pseudocode:
NSManagedObject *newObject = [[NSManagedObject alloc] initWithEntity:NSENTITYDESCRIPTION insertIntoManagedObjectContext:MANAGEDOBJECTCONTEXT];
[newObject setValue:#"VALUE_OF_SELECTED_OBJECT" forKey:#"APPROPRIATE_KEY"];
//REPEAT AS NECESSARY
[MANAGEDOBJECTCONTEXT save];
*Code not tested, naming conventions are ignored, etc.
The save is important. If you don't do this the object won't persist.