iOS Objective-C NSURLSession not updating UI / property - ios

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;

Related

Objective C - How to return local variable in this code?

I have the following code that works. It successfully displays myName in the NSLog ...
NSURL *apiString = [NSURL URLWithString:#"http://testapi.com/url"];
[XMLConverter convertXMLURL:apiString completion:^(BOOL success, NSDictionary *dictionary, NSError *error)
{
if (success)
{
NSString *myName = dictionary[#"profile"][#"real_name"];
NSLog(#"%# is my name", myName);
}
}];
I have the following code for the method convertXMLURL which is in XMLConverter.m which I imported. It does a nice job of converting my XML to NSDictionary. That is what I want ...
+ (void)convertXMLURL:(NSURL *)url completion:(OutputBlock)completion
{
///Wrapper for -initWithContentsOfURL: method of NSXMLParser
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSXMLParser *parser = [[NSXMLParser alloc] initWithContentsOfURL:url];
[[XMLConverter new] parser:parser completion:completion];
});
}
The problem I have is *dictionary is a local variable. I need to use it elsewhere in the code. How can I return it?
Simply create a #property and assign dictionary to it inside your completion handler.
For example:
Class Foo
#interface Foo : NSObject
#property (strong, nonatomic) NSDictionary* dictionary;
#end
Completion handler
NSURL *apiString = [NSURL URLWithString:#"http://testapi.com/url"];
[XMLConverter convertXMLURL:apiString completion:^(BOOL success, NSDictionary *dictionary, NSError *error)
{
if (success)
{
NSString *myName = dictionary[#"profile"][#"real_name"];
NSLog(#"%# is my name", myName);
myInstanceOfFoo.dictionary = dictionary;
}
}];
EDIT: If the completion handler is not inside a member of the Foo class the dictionary property must be declared in the header file (.h). Otherwise you can declare it in the implementation file (.m).
Based on the comment by carlodurso

Memory leak when making NSURLSession calls and loading images to NSImage

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?

How to Fetch data out from block in AFNetworking ios?

Creating first app with webservices, I am using AFNetworking for webservices. Everything is working fine but i have no idea , that how to fetch data out from block which i am getting in response. This is what i have done so far
+(WebServices *)sharedManager{
static WebServices *managerServices = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
managerServices = [[self alloc] init];
});
return managerServices;
}
-(NSArray *)firstPostService{
//1
NSURL *url = [NSURL URLWithString:BaseURLString];
//2
AFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc] initWithBaseURL:url];
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
NSDictionary *param = #{#"request" : #"get_pull_down_menu" , #"data" : #"0,0,3,1"};
[manager POST:#"person.php" parameters:param success:^(NSURLSessionDataTask *task, id responseObject) {
[self methodUsingJsonFromSuccessBlock:responseObject];
} failure:^(NSURLSessionDataTask *task, NSError *error) {
UIAlertView *av = [[UIAlertView alloc] initWithTitle:#"Error retrieving data" message:[error localizedDescription] delegate:nil cancelButtonTitle:#"Ok" otherButtonTitles:nil, nil];
[av show];
}];
if (list.count == 0) {
NSLog(#"Nothing in array yet!!");
}
else{
NSLog(#"Object 1 is : %#", [list objectAtIndex:1]);
}
return list;
}
- (void)methodUsingJsonFromSuccessBlock:(id)json {
// use the json
NSString *string = [NSString stringWithUTF8String:[json bytes]];
NSLog(#"This is data : %#", string);
list = [string componentsSeparatedByString:#"\n"];
NSLog(#"After sepration first object: %#", [list objectAtIndex:1]);
//NSLog(#"json from the block : %#", json);
}
What i understand reading from different blogs and tuts, that block is a separate thread and what every i do finishes with it. I read some where that this is normally use for it
dispatch_async(dispatch_get_main_queue(), ^{
data = [string componentsSeparatedByString:#"\n"];
//WHERE DATA IS __block NSArray * data = [[NSArray alloc] init];
});
and i was returning it in the of the function(firstPostService) but nothing happen. i still get an empty array outside the block. Kindly help me , suggest me some good reading stuff. Thanking you all in advance.
You say:
I need this data to my view controller i am trying to return in dispatch part but it is not allowing. Is it possible to get data into my viewcontroller class ?
Yes, it's possible. But, no, firstPostService should not return the results. It can't because it returns immediately, but the POST completion blocks won't be called until much later. There's nothing to return by the time firstPostService returns.
At the end of your original question, you said:
What i understand reading from different blogs and tuts, that block is a separate thread and what every i do finishes with it. I read some where that this is normally use for it
dispatch_async(dispatch_get_main_queue(), ^{
data = [string componentsSeparatedByString:#"\n"];
//WHERE DATA IS __block NSArray * data = [[NSArray alloc] init];
});
This is not the appropriate pattern of __block local variable. You generally use that __block pattern when dealing with some block that runs synchronously (for example the block of an enumeration method). But while you can use __block variable with asynchronous block, you almost never do (and it doesn't quite make sense to even try to do it). When you use appropriate completion block patterns, there's no need for any __block variable.
So, let's go back to your original code sample: So, you should take a page from AFNetworking and employ completion blocks yourself. When the AFNetworking POST method wanted to return data to your code asynchonously, it used a completion block pattern, instead. Thus, if your own firstPostService wants to pass back data asynchronously, it should do the same.
For example:
#interface WebServices ()
#property (nonatomic, strong) AFHTTPSessionManager *manager;
#end
#implementation WebServices
// note, use `instancetype` rather than actually referring to WebServices
// in the `sharedManager` method
+ (instancetype)sharedManager
{
static id sharedMyManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedMyManager = [[self alloc] init];
});
return sharedMyManager;
}
// I'd also suggest that you init the `AFHTTPSessionManager` only once when this
// object is first instantiated, rather than doing it when `firstPostService` is
// called
- (instancetype)init
{
self = [super init];
if (self) {
NSURL *url = [NSURL URLWithString:BaseURLString];
self.manager = [[AFHTTPSessionManager alloc] initWithBaseURL:url];
self.manager.responseSerializer = [AFHTTPResponseSerializer serializer];
}
return self;
}
// Notice:
//
// 1. This now has a return type of `void`, because when it instantly returns,
// there is no data to return.
//
// 2. In order to pass the data back, we use the "completion handler" pattern.
- (void)firstPostServiceWithCompletionHandler:(void (^)(NSArray *list, NSError *error))completionHandler {
NSDictionary *param = #{#"request" : #"get_pull_down_menu" , #"data" : #"0,0,3,1"};
[self.manager POST:#"person.php" parameters:param success:^(NSURLSessionDataTask *task, id responseObject) {
NSArray *list = [self methodUsingJsonFromSuccessBlock:responseObject];
if (completionHandler) {
completionHandler(list, nil);
}
} failure:^(NSURLSessionDataTask *task, NSError *error) {
[[[UIAlertView alloc] initWithTitle:#"Error retrieving data" message:[error localizedDescription] delegate:nil cancelButtonTitle:#"Ok" otherButtonTitles:nil, nil] show];
if (completionHandler) {
completionHandler(nil, error);
}
}];
// // none of this code belongs here!!! You are dealing with asynchronous methods.
// // the `list` has not been returned by the time you get here!!! You shouldn't even
// // be using instance variable anyway!
//
// if (list.count == 0) {
//
// NSLog(#"Nothing in array yet!!");
// }
// else{
// NSLog(#"Object 1 is : %#", [list objectAtIndex:1]);
//
// }
// return list;
}
- (NSArray *)methodUsingJsonFromSuccessBlock:(NSData *)data {
// note, do not use `stringWithUTF8String` with the `bytes` of the `NSData`
// this is the right way to convert `NSData` to `NSString`:
NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"This is string representation of the data : %#", string);
// Note, retire the `list` instance variable, and instead use a local variable
NSArray *list = [string componentsSeparatedByString:#"\n"];
NSLog(#"After sepration first object: %#", [list objectAtIndex:1]);
return list;
}
#end
Then, you could invoke that like so:
[[WebServices sharedManager] firstPostServiceWithCompletionHandler:^(NSArray *list, NSError *error) {
if (error) {
// handle the error here
} else {
// use the `list` results here
}
}];
// NOTE, DO NOT USE `list` HERE. By the time you get here, `list` has not been
// returned. Only use it in the above block.
//
// In fact, you can see that if you put a `NSLog` or breakpoint here, and again, above
// where it says "use the `list` results` here", you'll see that it's running the code
// inside that block _after_ this code down here!
I'd suggest you tackle the above first, to first make sure you completely understand the proper asynchronous technique of the completion block pattern. We don't want to complicate things quite yet. Make sure you're getting the sort of data you wanted before you proceed to what I will describe below.
But, once you've grokked the above, it's time to look at your JSON parsing. You make several reference to JSON, but if that's what it really is, then using componentsSeparatedByString is not the right way to parse it. You should use NSJSONSerialization. Or even better, you can let AFNetworking do that for you (right now, you're making it more complicated than it needs to be and your results will not be formatted correctly).
Above, I kept your methodUsingJsonFromSuccessBlock in the process, but if you're really dealing with JSON, you should eliminate that method entirely. Let AFNetworking do this for you.
You should eliminate the line that says:
responseSerializer = [AFHTTPResponseSerializer serializer];
The default serializer is AFJSONResponseSerializer which is what you want to use if handling JSON requests.
The methodUsingJsonFromSuccessBlock is then no longer needed because AFNetworking will do the JSON conversion for you. So firstPostServiceWithCompletionHandler should look like:
- (void)firstPostServiceWithCompletionHandler:(void (^)(NSArray *list, NSError *error))completionHandler {
NSDictionary *param = #{#"request" : #"get_pull_down_menu" , #"data" : #"0,0,3,1"};
[self.manager POST:#"person.php" parameters:param success:^(NSURLSessionDataTask *task, id responseObject) {
if (completionHandler) {
completionHandler(responseObject, nil);
}
} failure:^(NSURLSessionDataTask *task, NSError *error) {
[[[UIAlertView alloc] initWithTitle:#"Error retrieving data" message:[error localizedDescription] delegate:nil cancelButtonTitle:#"Ok" otherButtonTitles:nil, nil] show];
if (completionHandler) {
completionHandler(nil, error);
}
}];
}

AFHTTPSessionManager with multiple requests in rapid succession (AFNetworking 2.0)

i am new to iOS programming, still learning.
EDIT: !!!!!! Everything in my code works. My question is about the delegation pattern i use,
if i am generating problems in the background that i have no idea of, or if there is a better way to handle my situation in AFNetworking...
I have created an API for my app by subclassing AFHTTPSessionManager.
My API creates a singleton and returns it and supplies public functions for various requests. And those functions create parameter lists, and make GET requests on the server like this:
- (void)getCharacterListForKeyID:(NSString *)keyID vCode:(NSString *)vCode sender:(id)delegate
{
NSMutableDictionary *parameters = [NSMutableDictionary dictionary];
parameters[#"keyID"] = keyID;
parameters[#"vCode"] = vCode;
[self GET:#"account/Characters.xml.aspx" parameters:parameters success:^(NSURLSessionDataTask *task, id responseObject) {
self.xmlWholeData = [NSMutableDictionary dictionary];
self.errorDictionary = [NSMutableDictionary dictionary];
NSXMLParser *XMLParser = (NSXMLParser *)responseObject;
[XMLParser setShouldProcessNamespaces:YES];
XMLParser.delegate = self;
[XMLParser parse];
if ([delegate respondsToSelector:#selector(EVEAPIHTTPClient:didHTTPRequestWithResult:)]) {
[delegate EVEAPIHTTPClient:self didHTTPRequestWithResult:self.xmlWholeData];
}
} failure:^(NSURLSessionDataTask *task, NSError *error) {
if ([delegate respondsToSelector:#selector(EVEAPIHTTPClient:didFailWithError:)]) {
[delegate EVEAPIHTTPClient:self didFailWithError:error];
}
}];
}
I was using a normal protocol/delegate method earlier. But once i make calls this API more than once like this: (IT WAS LIKE THIS:)
EVEAPIHTTPClient *client = [EVEAPIHTTPClient sharedEVEAPIHTTPClient];
client.delegate = self;
[client getCharacterListForKeyID:self.keyID vCode:self.vCode];
Previous call's delegate was being overwritten by next. So i changed to above style. Passing sender as an argument in the function:
EVEAPIHTTPClient *client = [EVEAPIHTTPClient sharedEVEAPIHTTPClient];
[client getCharacterListForKeyID:self.keyID vCode:self.vCode sender:self];
And i pass this sender to GET request's success and failure blocks.
What i wonder is : "Is this a good programming practice ?". Passing objects to blocks like this should be avoided if possible ? Is there any other more elegant way in AFHTTPSessionManager to handle this type of work (making same GET request over and over with different parameters and returning results to the respective request owners) more elegantly ?
Delegation pattern falters when it comes to simplicity and asynchronous request processing. You should be using blocks, here's an example
Your server class:
static NSString *const kNews = #"user_news/"; // somewhere above the #implementation
- (NSURLSessionDataTask *)newsWithPage:(NSNumber *)page
lastNewsID:(NSNumber *)lastNewsID
completion:(void (^)(NSString *errMsg, NSArray *news, NSNumber *nextPage))completionBlock {
return [self GET:kNews
parameters:#{#"page" : page,
#"news_id" : lastNewsID
}
success:^(NSURLSessionDataTask *task, id responseObject) {
NSArray *news = nil;
NSNumber *nextPage = nil;
NSString *errors = [self errors:responseObject[#"errors"]]; // process errors
if ([responseObject[#"status"] boolValue]) {
news = responseObject[#"news"];
nextPage = responseObject[#"next_page"];
[self assignToken];
}
completionBlock(errors, news, nextPage);
}
failure:^(NSURLSessionDataTask *task, NSError *error) {
NSString *errors = [self errors:error];
completionBlock(errors, nil, nil);
}];
}
The caller
- (void)dealloc {
[_task cancel]; // you don't want this task to execute if user suddenly removes your controller from the navigation controller's stack
}
- (void)requestNews {
typeof(self) __weak wself = self; // to avoid the retain cycle
self.task = [[GSGServer sharedInstance] newsWithPage:self.page
lastNewsID:self.lastNewsID
completion:^(NSString *errMsg, NSArray *news, NSNumber *nextPage) {
if (errMsg) {
[GSGAppDelegate alertQuick:errMsg]; // shortcut for posting UIAlertView, uses errMsg for message and "Error" as a title
return;
}
[wself.news addObjectsFromArray:news];
wself.lastNewsID = [wself.news firstObject][#"id"];
wself.page = nextPage;
[wself.tableView reloadData];
}];
}

How do I get a variable out of a completion block? [duplicate]

This question already has answers here:
Change value of NSString inside block
(2 answers)
Closed 9 years ago.
(void)fetchLastMessageInChannel
{
__weak id weakSelf = self;
for (ANKChannel *channel in self.channelArray)
{
NSLog(#"channels %#",channel);
NSLog(#"channel last message %#",channel.latestMessageID);
[[ClientManager currentClient] fetchMessageWithID:channel.latestMessageID inChannel:channel
completion:^(id responseObject, ANKAPIResponseMeta *meta, NSError *error)
{
NSLog(#"message object %#",responseObject);
ANKMessage *message = responseObject;
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf populateTextViews:message.text];
});
NSLog(#"message text %#",message.text);
}];
}
}
-(void)populateTextViews:(NSString *)message
{
NSMutableArray *textViews = [#[] mutableCopy];
NSMutableAttributedString *postText = [[NSMutableAttributedString alloc] initWithString:message];
[postText addAttributes:#{
NSFontAttributeName : [UIFont preferredFontForTextStyle:UIFontTextStyleBody],
NSForegroundColorAttributeName : [UIColor darkTextColor]
}
range:NSMakeRange(0, postText.length)];
UITextView *postTextView = [[UITextView alloc] initWithFrame:CGRectMake(80, 30, kPostLabelMaxWidth, 44)];
postTextView.attributedText = postText;
postTextView.dataDetectorTypes = UIDataDetectorTypeAll;
postTextView.backgroundColor = [UIColor whiteColor];
postTextView.editable = NO;
postTextView.scrollEnabled = NO;
postTextView.clipsToBounds = NO; // So it doesn't clip the text selector
CGRect textViewBounds = postTextView.bounds;
textViewBounds.origin = CGPointMake(80, 30);
textViewBounds.size.width = MAX(textViewBounds.size.width, kPostLabelMaxWidth);
textViewBounds.size.height = postTextView.contentSize.height;
postTextView.bounds = textViewBounds;
[postTextView sizeToFit]; // Reload the content size
[textViews addObject:postTextView];
self.channelTextViewArray = [textViews copy];
}
This is where I stand now as far as my methods go with the help I've received. The self.channelTextViewArray is returning nil and causing a crash because the populateTextViews(NSString*)message never gets called.
Any ideas?
If ClientManager call is async, the populateTextViews method will complete before the async call returns, which is why you can't use the value set in its completion block.
Either put...
NSMutableAttributedString *postText = [[NSMutableAttributedString alloc] initWithString:messageText];
...inside the completion block, or call out to a method inside the completion block once you have the messageText. In doing that you will not have to declare the __block variable either.
If there will be a UI update, make sure that happens on the main thread.
EDIT
This the basic idea, but I'm guessing you're updating more than one text view so you may need to change the signatures around. If you get the basic idea -- calling an async method doesn't interrupt the flow of your code (basically it says "do this when you have a chance, possibly on another thread"). That's the reason you have a completion block -- it's a place in your code where you know that the async method you called has completed.
If what's inside the block isn't getting called at all, make sure that self.channelArray has values, and look at what fetchMessageWithID does if something goes wrong.
- (void)populateTextViews
{
__weak id weakSelf = self;
for (ANKChannel *channel in self.channelArray)
{
[[ClientManager currentClient] fetchMessageWithID:channel.latestMessageID inChannel:channel
completion:^(id responseObject, ANKAPIResponseMeta *meta, NSError *error)
{
NSLog(#"message object %#",responseObject);
ANKMessage *message = responseObject;
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf updateTextView:message.text];
});
}];
}
}
- (void)updateTextView:(NSString *)message
{
// Make an attributed string from the post text content
NSMutableAttributedString *postText = [[NSMutableAttributedString alloc] initWithString:messageText];
self.textView.attributedText = postText;
}

Resources