I'm new to iOS development and to stack overflow. I need to show user's data in multiple UILabel and UITextField, the data is obtained from a POST method. There is a slight delay for getting the data from the server. How to reload or populate the elements after getting the details.
This is my viewDidLoad()
#interface EditProfileViewController ()
#property (weak, nonatomic) IBOutlet UIImageView *displayPictureView;
#property (weak, nonatomic) IBOutlet UITextField *firstnameFeld;
#property (weak, nonatomic) IBOutlet UITextField *lastNameField;
#property (weak, nonatomic) IBOutlet UITextField *emailField;
#property (weak, nonatomic) IBOutlet UITextField *bdayField;
#property (weak, nonatomic) IBOutlet UIButton *calField;
#property (weak, nonatomic) IBOutlet UITextView *addressTextView;
#property (weak, nonatomic) IBOutlet UILabel *userIDLabel;
#property (weak, nonatomic) IBOutlet UILabel *phoneNumberLabel;
#property (weak, nonatomic) IBOutlet UIScrollView *editScrollView;
#end
#implementation EditProfileViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.editScrollView.delegate=self;
}
I receive data from the server using the code, i have this POST method in viewDidLoad()
NSURLSessionConfiguration *defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *defaultSession = [NSURLSession sessionWithConfiguration: defaultConfigObject delegate: nil delegateQueue: [NSOperationQueue mainQueue]];
NSMutableURLRequest * urlRequest = [NSMutableURLRequest requestWithURL:urlEdit];
[urlRequest setHTTPMethod:#"POST"];
[urlRequest setHTTPBody:[editParameters dataUsingEncoding:NSUTF8StringEncoding]];
NSURLSessionDataTask * dataTask =[defaultSession dataTaskWithRequest:urlRequest
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSLog(#"Response:%# \nError:%#\n", response, error);
if(error == nil)
{
NSString * text = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding];
NSLog(#"DataText = %#",text);
}
NSError *error2 = nil;
jsonDicEditAcc = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error2];
if (error2 != nil) {
NSLog(#"Error parsing JSON.");
}
else {
NSLog(#"JsonDictEditAcc: \n%#",jsonDicEditAcc);
[self dismissViewControllerAnimated:alert completion:nil];}
}];
[dataTask resume];
I will extract data from the "jsonDicEditAcc", i need to know how to populate the UILabels and UITextField, after getting the data because the view gets loaded before getting the data. I have tried viewWillAppear() did not work. Help me out.. Thanks in advance..
You'll need to know the content of the data you're trying to apply. If you know the key's that associate with each value you can apply those values to the text fields (and labels and buttons) and be done. They will change when you successfully update them.
Inside your block you need to trigger the population of the text fields. You can add something like this:
else {
NSLog(#"JsonDictEditAcc: \n%#",jsonDicEditAcc);
[self updateLabelsAndTextFields];
[self dismissViewControllerAnimated:alert completion:nil];}
}];
This function that will extract the data from jsonDicEditAcc and update the text of each relevant UILabel. The view has already loaded, but the UILabel will update when you change the text.
A solution could look like this:
- (void)updateLabelsAndTextFields{
self.firstnameFeld.text = jsonDicEditAcc[#"key"];
self.lastNameField.text = jsonDicEditAcc[#"key"];
self.emailField.text = jsonDicEditAcc[#"key"];
self.bdayField.text = jsonDicEditAcc[#"key"];
self.userIDLabel.text = jsonDicEditAcc[#"key"];
[calField setTitle:jsonDicEditAcc[#"key"] forState:UIControlStateNormal];
}
Be sure name a UIButton a button, i.e. "calButton" not "calField"
It's pretty straightforward, add similar code to the else branch of your if (error2 != nil) statement:
weakSelf.firstnameFeld = jsonDicEditAcc[#"<key-for-firstname>"];
Also, you might find it necessary to remove this statement from that branch as it will close the current viewController:
[self dismissViewControllerAnimated:alert completion:nil];
And I might point out, you need to use weakSelf inside of blocks to prevent memory leaks. Using self inside a block creates a retain cycle which means your entire viewController remains allocated until iOS eventually crashes your app for excessive memory use.
Put this at the top of your method and it is accessible inside the block, and then change all references of self inside of blocks to weakSelf.
typeof(self) __weak weakSelf = self;
Related
I'm having some trouble updating my UI in my Objective-C iOS app.
Im calling my function fetchData in the viewDidLoad section of my ViewController.
The NSURLSession succesfully fetches the data however I am unable to update the titleText property in my UI even though its on the main queue.
I can update property in the viewDidLoad method, So I have a feeling this is something to do with the asynchronous request.
However I have tried multiple ways with no luck so any help would be much appreciated.
- (void) fetchData
{
NSString *strURl = #"www.url.com";
NSLog(#"%#",strURl);
NSURLSession *session = [NSURLSession sharedSession];
[[session dataTaskWithURL:[NSURL URLWithString:strURl]
completionHandler:^(NSData *data,
NSURLResponse *response,
NSError *error) {
if (data == nil) {
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(#"error", nil)message:NO_CONNECTION_TEXT preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* ok = [UIAlertAction actionWithTitle:NSLocalizedString(#"ok", nil) style:UIAlertActionStyleDefault handler:nil];
[alertController addAction:ok];
[self presentViewController:alertController animated:YES completion:nil];
return ;
}
else
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
if (!parsedDetails){
parsedDetails = [[NSMutableArray alloc]init];
}
parsedDetails = [provider parseJSON:data into:parsedDetails withResponse:response];
NSLog(#"%#",parsedDetails);
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"%#",parsedDetails);
NSLog(#"%#",[parsedDetails valueForKey:#"title"]);
self.titleText = [parsedDetails valueForKey:#"title"];
});
});
}
}] resume];
}
Part 1: Determining the title
parsedDetails is a NSMutableArray in your code.
Then you are using the KVC (Key-Value Coding) method valueForKey on that array.
But that does return a new array. What happens there: the value of the title property for each element in the original array (parsedDetails) is inserted as a new element in a new array which is finally returned as array.
Due to your comment above:
NSLog(#"%#",[parsedDetails valueForKey:#"title"]);
gives the return value (an array with one string):
("About")
But what you actually want is the title element of the first entry, so rather something like this:
Detail *detail = parsedDetails.firstObject;
if (detail) {
title = [detail title];
} else {
title = #"default empty title";
}
Or if you prefer the KVC variant:
id detail = parsedDetails.firstObject;
if (detail) {
title = [detail valueForKey:#"title"];
} else {
title = #"default empty title";
}
Part 2: Updating the label
As far as I understand your comments, you have a NSString property called titleText? Presumably defined like this:
#property (nonatomic, strong) IBOutlet NSString *titleText;
And you also have somewhere a UILabel outlet?
#property (weak, nonatomic) IBOutlet UILabel *label;
If you just update the local string it will not be reflected in the UI. You yet have to transfer it to the label.
This would look like so:
self.label.text = self.titleText;
I'm attempting to access data from CMPedometer. I have a class called StepService which has the following property
#property (strong, nonatomic) CMPedometer *pedometer;
Which has the following getter
-(CMPedometer*) pedometer{
if(!_pedometer){
_pedometer = [[CMPedometer alloc] init];
}
return _pedometer;
}
I'm using the following code to get the steps:
-(void) storeData {
[self.pedometer queryPedometerDataFromDate:[[NSCalendar currentCalendar] startOfDayForDate:[NSDate date]]
toDate:[NSDate date]
withHandler:^(CMPedometerData *pedometerData, NSError *err){
if (err) {
NSLog(#"Error getting pedometer data: %#", err);
} else {
...
}
}];
}
When I call the above code like so:
StepService *stepService = [[StepService alloc] init];
[stepService storeData];
I get the following value for err
Error Domain=CMErrorDomain Code=103 "(null)"
Debugging reveals pedometer is not null but pedometerData is. What is going on here, how can I resolve this error?
Following Larme's advice, I created a property in my ViewController and synthesized it, like so:
#interface MainViewController(){
#property (strong, nonatomic) StepService *stepService;
#end
#implementation MainViewController
#synthesize stepService
I was then able to create and call the method containing queryPedometerDataFromDate like so:
stepService = [[StepService alloc] init];
[stepService storeData];
I have a UITableView with 2 text field and a button. If I run the simulator without use custom class, I can see the text fields and button:
But when i use a custom class, my UITable view only display a lot of lines without content:
Here is how I've created my properties:
LoginSceneController.h
#import <UIKit/UIKit.h>
#interface LoginSceneController : UITableViewController
#property (nonatomic, strong) IBOutlet UITextField *email;
#property (nonatomic, strong) IBOutlet UITextField *password;
- (IBAction)doLogin;
#end
LoginSceneController.m
#import "LoginSceneController.h"
#interface LoginSceneController ()
#end
#implementation LoginSceneController
- (void)viewDidLoad {
[super viewDidLoad];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
- (void)doLogin {
NSURL *url = [NSURL URLWithString:#"http://rest-service.guides.spring.io/greeting"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response,
NSData *data, NSError *connectionError)
{
if (data.length > 0 && connectionError == nil)
{
NSDictionary *greeting = [NSJSONSerialization JSONObjectWithData:data
options:0
error:NULL];
self.email.text = [[greeting objectForKey:#"id"] stringValue];
self.password.text = [greeting objectForKey:#"content"];
}
}];
}
#end
The problem happens when I use a custom class (or referencing outlet or add a send event on button).
What is wrong?
edit: I think that I need populate my interface using my custom class because the static content is being lost. Is it possible to be the cause of content being lost?
You have two options when it comes to UITableView and Interface Builder. You can have a dynamic table view (pretty common) where your code overrides UITableViewController methods like "numberOfRowsInSection" and "cellForRowAtIndexPath". The other option is a static tableview, and that seems like what you want to do (especially since you haven't overridden the two aforementioned methods, and leads to your blank table). My guess is you need to select "static" for the tableview as shown in the third screenshot in this tutorial.
I've built a small demo-application which allows the user to choose a color, which is sent to a basic (for now localhost) node.js server (using NSURLSessionDataTask), which uses the color name to get a fruit name and image URL, and return a simple 2 property JSON object containing the two.
When the application receives the JSON response, it creates a sentence with the color name and fruit name to display in the GUI, and then spawns another NSURLSession call (this time using NSURLSessionDownloadTask) to consume the image URL and download a picture of the fruit to also display in the GUI.
Both of these network operations use [NSURLSession sharedSession].
I'm noticing that both the JSON call and more noticeably the image download are leaking significant amounts of memory. They each follow a similar pattern using nested blocks:
Initialize the session task, passing a block as the completion handler.
If I understand correctly, the block is run on a separate thread since the communication in NSURLSession is async by default, so updating the GUI has to happen in the main, so within the completeHandler block, a call to dispatch_async is made, specifying the main thread, and a short nested block that makes a call to update the GUI.
My guess is that either my use of nested blocks, or nesting of GCD calls is causing the issue. Though it's entirely possible my problem is multi-faceted.
Was hoping some of you with more intimate knowledge of how Obj-C manages memory with threads and ARC would be greatly helpful. Relevant code is included below:
AppDelegate.m
#import "AppDelegate.h"
#import "ColorButton.h"
#interface AppDelegate ()
#property (weak) IBOutlet NSWindow *window;
#property (weak) IBOutlet NSImageView *fruitDisplay;
#property (weak) IBOutlet NSTextField *fruitNameLabel;
#property (weak) IBOutlet ColorButton *redButton;
#property (weak) IBOutlet ColorButton *orangeButton;
#property (weak) IBOutlet ColorButton *yellowButton;
#property (weak) IBOutlet ColorButton *greenButton;
#property (weak) IBOutlet ColorButton *blueButton;
#property (weak) IBOutlet ColorButton *purpleButton;
#property (weak) IBOutlet ColorButton *brownButton;
#end
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
proxy = [[FruitProxy alloc] init];
}
- (void)applicationWillTerminate:(NSNotification *)aNotification
{
// Insert code here to tear down your application
}
-(BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender
{
return YES;
}
/*------------------------------------------------------------------*/
- (IBAction)colorButtonWasClicked:(id)sender
{
ColorButton *btn = (ColorButton*)sender;
NSString *selectedColorName = btn.colorName;
#autoreleasepool {
[proxy requestFruitByColorName:selectedColorName
completionResponder:^(NSString* fruitMessage, NSString* imageURL)
{
[self fruitNameLabel].stringValue = fruitMessage;
__block NSURLRequest *req = [NSURLRequest requestWithURL:[NSURL URLWithString:imageURL]];
__block NSURLSession *imageSession = [NSURLSession sharedSession];
__block NSURLSessionDownloadTask *imgTask = [imageSession downloadTaskWithRequest:req
completionHandler:
^(NSURL *location, NSURLResponse *response, NSError *error)
{
if(fruitImage != nil)
{
[self.fruitDisplay setImage:nil];
fruitImage = nil;
}
req = nil;
imageSession = nil;
imgTask = nil;
response = nil;
fruitImage = [[NSImage alloc] initWithContentsOfURL:location];
[fruitImage setCacheMode:NO];
dispatch_async
(
dispatch_get_main_queue(),
^{
[[self fruitDisplay] setImage: fruitImage];
}
);
}];
[imgTask resume];
}];
}
}
#end
FruitProxy.m
#import "FruitProxy.h"
#implementation FruitProxy
- (id)init
{
self = [super init];
if(self)
{
return self;
}
else
{
return nil;
}
}
- (void) requestFruitByColorName:(NSString*)colorName
completionResponder:(void( ^ )(NSString*, NSString*))responder
{
NSString *requestURL = [self urlFromColorName:colorName];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:requestURL]];
session = [NSURLSession sharedSession];
#autoreleasepool {
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:
^(NSData *data, NSURLResponse *response, NSError *connectionError)
{
NSString *text = [[NSString alloc] initWithData:data
encoding:NSUTF8StringEncoding];
NSDictionary *responseObj = (NSDictionary*)[NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSString *fruitName = (NSString*)responseObj[#"fruitName"];
NSString *imageURL = (NSString*)responseObj[#"imageURL"];
NSLog(#"Data = %#",text);
dispatch_async
(
dispatch_get_main_queue(),
^{
responder([self messageFromColorName:colorName fruitName:fruitName], imageURL);
}
);
}];
[task resume];
}
}
- (NSString*)urlFromColorName:(NSString*)colorName
{
NSString *result;
result = #"http://localhost:9000/?color=";
result = [result stringByAppendingString:colorName];
return result;
}
- (NSString*)messageFromColorName:(NSString*)colorName
fruitName:(NSString*)fruitName
{
NSString *result = #"A ";
result = [[[[result stringByAppendingString:colorName]
stringByAppendingString:#"-colored fruit could be "]
stringByAppendingString:fruitName]
stringByAppendingString:#"!"];
return result;
}
#end
Where does "fruitImage" come from in AppDelegate.m? I don't see it declared.
the line:
__block NSURLSessionDownloadTask *imgTask
is a bit weird because you're marking imgTask as a reference that can change in the block, but it's also the return value. That might be part of your problem, but in the very least it's unclear. I might argue that all the variables you marked __block aren't required to be as such.
typically a memory leak in these situations is caused by the variable capture aspect of the block, but I'm not seeing an obvious offender. The "Weak Self" pattern might help you here.
Using "leaks" might help you see what objects are leaking, which can help isolate what to focus on, but also try to take a look at your block's life cycles. If a block is being held by an object it can create cycles by implicitly retaining other objects.
Please follow up when you figure out exactly what's going on.
reference:
What does the "__block" keyword mean?
Always pass weak reference of self into block in ARC?
Ive been stuck on this problem for a good while now, I read a bunch of threads but none describe my problem I tried a whole bunch of different methods to do it but none worked. I have a PFFile that I pulled from array and sent through a segue to a download detail view. This file is called "download file".I am trying to program a button when clicked to initiate the download. here is the code:
this is my download detail.h
#import <UIKit/UIKit.h>
#import <Parse/Parse.h>
#interface PDFDetailViewController : UIViewController {
}
#property (strong, nonatomic) IBOutlet UILabel *PDFName;
#property (strong, nonatomic) IBOutlet UILabel *PDFDescription;
#property (strong, nonatomic) NSString* PDFna;
#property (strong, nonatomic) NSString* PDFdes;
#property (retain,nonatomic) PFFile * downloadfile;
- (IBAction)Download:(id)sender;
#end
my download detail button
- (IBAction)Download:(id)sender {
[self Savefile];
}
-(void) Savefile {
NSData *data = [self.downloadfile getData];
[data writeToFile:#"Users/Danny/Desktop" atomically:NO];
NSLog(#"Downloading...");
}
#end
and here is the segue that sends the download file:
detailVC.downloadfile=[[PDFArray objectAtIndex:indexPath.row]objectForKey:#"PDFFile"];
I get the array data using the PFQuery and store it into "PDFArray". This is a synchronous download because a warning message comes up when i click the button saying that main thread is being used. Although the file doesn't show up on my desktop.
Have you tried using this Parse method?
getDataInBackgroundWithBlock:
-(void) Savefile {
[self.downloadfile getDataInBackgroundWithBlock:^(NSData *data, NSError *error) {
if (error) {
// handle error
}
else if (data) {
[data writeToFile:#"Users/Danny/Desktop" atomically:NO];
}
}];
}