I know this is probably going to be one big headache, but it needs to be done. I finally got my tableview resizing cells as well as my label in such a way that all the text from each cell is displayed correctly.
NOTE: the comment & bump buttons are NOT the popdown menu I want to displayed when the cell is expanded , those 2 buttons should always be displayed , the menu i want to add would be a set of 4 buttons that would display under those buttons when the cell is expanded
But now I would like to add a pop down menu to each cell like tweetbot, shown below
But this is proving to be quite difficult due to how i am resizing the label using autolayout constraints. I came across this example of how to implement something similar to Tweetbot that seems pretty simple and practical on github here.
The problem is this example assumes all cells are the same size and that their is no dynamic sizing to the individual cells.
PublicFeedViewController.M
#import "PublicFeedViewController.h"
#import "FeedItemCell.h"
#import "AFNetworking.h"
#import "UIImageView+WebCache.h"
#import "InboxDetailViewController.h"
#import "SWRevealViewController.h"
#import "CommentsViewController.h"
#import "NSDate+TimeAgo.h"
#interface PublicFeedViewController (){
NSArray *NameLabel;
NSArray *StatusLabel;
NSMutableArray *feedArray;
}
#end
#implementation PublicFeedViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
//The below code prompts the user for push notifications. If allowed, code in AppDelegate takes over and stores the token.
[[UIApplication sharedApplication] registerForRemoteNotificationTypes:(UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound | UIRemoteNotificationTypeAlert)];
// Do any additional setup after loading the view.
self.FeedTable.dataSource=self;
self.FeedTable.delegate=self;
// Set the side bar button action. When it's tapped, it'll show up the sidebar.
_sidebarButton.target = self.revealViewController;
_sidebarButton.action = #selector(revealToggle:);
// Set the gesture
[self.view addGestureRecognizer:self.revealViewController.panGestureRecognizer];
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
NSDictionary *parameters = #{#"foo": #"bar"};
[UIApplication sharedApplication].networkActivityIndicatorVisible = TRUE;
[manager POST:#"http://" parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject) {
//NSLog(#"JSON: %#", responseObject);
self->feedArray = [responseObject objectForKey:#"feed"];
[self.FeedTable reloadData];
[UIApplication sharedApplication].networkActivityIndicatorVisible = FALSE;
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
return 1;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return feedArray.count;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
NSString *CellIdentifier=#"Cell";
FeedItemCell *Cell=[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(!Cell){
Cell = [[FeedItemCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
NSLog(#"FEED ARRAY: %#", self->feedArray);
NSDictionary *tempDictionary= [self->feedArray objectAtIndex:indexPath.row];
// Display recipe in the table cell
NSString *thumb_img = [tempDictionary objectForKey:#"thumb_img"];
NSString *thumb_path=#"http://";
NSString *thumb_url = [thumb_path stringByAppendingString:thumb_img];
Cell.NameLabel.text=[tempDictionary objectForKey:#"first_name"];
Cell.StatusLabel.text=[tempDictionary objectForKey:#"message"];
Cell.msg_id=[tempDictionary objectForKey:#"msg_id"];
//Cell.status=[tempDictionary objectForKey:#"message"];
Cell.StatusLabel.lineBreakMode=0;
Cell.StatusLabel.numberOfLines=0;
NSString *commentCount = [tempDictionary objectForKey:#"comment_count"];
NSString *commentButtonText =[NSString stringWithFormat:#"Comments ( %# )",commentCount];
[Cell.commentButton setTitle:commentButtonText forState: UIControlStateNormal];
NSString *bumpCount = [tempDictionary objectForKey:#"bump_count"];
NSString *bumpButtonText =[NSString stringWithFormat:#"Bumps ( %# )",bumpCount];
[Cell.bumpButton setTitle:bumpButtonText forState: UIControlStateNormal];
//[Cell.StatusLabel sizeToFit];
NSString *created_string=[tempDictionary objectForKey:#"created"];
double created_double = created_string.doubleValue;
NSDate *date = [[NSDate alloc] initWithTimeIntervalSince1970:created_double];
NSString *ago = [date timeAgo];
Cell.timeLabel.text=ago;
//Cell.DefaultImg.image = [UIImage imageNamed:#"buhz_mini_logo.png"];
[Cell.DefaultImg setImageWithURL:[NSURL URLWithString:thumb_url]
placeholderImage:[UIImage imageNamed:#".png"]];
return Cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
//Ideally you should do lazy loading so that instead of creating a new textView each time, you just reuse the same one.
UITextView *temp = [[UITextView alloc] initWithFrame:CGRectMake(82, 26, self.FeedTable.frame.size.width, 18)]; //This initial size doesn't matter
NSDictionary *tempDictionary= [self->feedArray objectAtIndex:indexPath.row];
NSString *status = [tempDictionary objectForKey:#"message"];
temp.font =[UIFont fontWithName:#"System" size:12];
temp.text = status;
[temp isHidden];
CGFloat textViewWidth = 218;
CGRect tempFrame = CGRectMake(82,26,textViewWidth,18); //The height of this frame doesn't matter.
CGSize tvsize = [temp sizeThatFits:CGSizeMake(tempFrame.size.width, tempFrame.size.height)]; //This calculates the necessary size so that all the text fits in the necessary width.
//Add the height of the other UI elements inside your cell
return tvsize.height + 70;
}
#end
CUSTOM FeedItemCell.M
#import "FeedItemCell.h"
#import "WYPopoverController/WYPopoverController.h"
#import "WYPopoverController/WYStoryboardPopoverSegue.h"
#import "CommentsViewController.h"
#import "NSDate+TimeAgo.h"
#interface FeedItemCell() <WYPopoverControllerDelegate>
{
WYPopoverController* commentsPopoverController;
}
- (IBAction)open:(id)sender;
- (void)close:(id)sender;
#end
#implementation FeedItemCell
#synthesize commentButton;
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
// Initialization code
}
return self;
}
-(IBAction)bump:(id)sender{
[self expand];
}
- (IBAction)open:(id)sender
{
[self showpopover:sender];
}
- (void)close:(id)sender
{
[commentsPopoverController dismissPopoverAnimated:YES];
commentsPopoverController.delegate = nil;
commentsPopoverController = nil;
}
-(void)expand
{
CGRect oldFrame = [self frame];
[self setFrame:CGRectMake( oldFrame.origin.x,
oldFrame.origin.y,
oldFrame.size.width,
oldFrame.size.height * 2)];
}
-(void)contract
{
CGRect oldFrame = [self frame];
[self setFrame:CGRectMake( oldFrame.origin.x,
oldFrame.origin.y,
oldFrame.size.width,
oldFrame.size.height / 2)];
}
- (IBAction)showpopover:(id)sender
{
if (commentsPopoverController == nil)
{
UIView *btn = (UIView*)sender;
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
CommentsViewController *commentsViewController = [storyboard instantiateViewControllerWithIdentifier:#"Comments"];
commentsViewController.msg_id=_msg_id;
if ([commentsViewController respondsToSelector:#selector(setPreferredContentSize:)]) {
commentsViewController.preferredContentSize = CGSizeMake(300, 500); // iOS 7
}
else {
commentsViewController.contentSizeForViewInPopover = CGSizeMake(300, 500); // iOS < 7
}
commentsViewController.title = #"Comments";
[commentsViewController.navigationItem setRightBarButtonItem:[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:#selector(close:)]];
commentsViewController.modalInPopover = NO;
UINavigationController* contentViewController = [[UINavigationController alloc] initWithRootViewController:commentsViewController];
commentsPopoverController = [[WYPopoverController alloc] initWithContentViewController:contentViewController];
commentsPopoverController.delegate = self;
commentsPopoverController.passthroughViews = #[btn];
commentsPopoverController.popoverLayoutMargins = UIEdgeInsetsMake(10, 10, 10, 10);
commentsPopoverController.wantsDefaultContentAppearance = NO;
[commentsPopoverController presentPopoverFromRect:btn.bounds
inView:btn
permittedArrowDirections:WYPopoverArrowDirectionNone
animated:YES
options:WYPopoverAnimationOptionFadeWithScale];
}
else
{
[self close:nil];
}
}
- (BOOL)popoverControllerShouldDismissPopover:(WYPopoverController *)controller
{
return YES;
}
- (void)popoverControllerDidDismissPopover:(WYPopoverController *)controller
{
if (controller == commentsPopoverController)
{
commentsPopoverController.delegate = nil;
commentsPopoverController = nil;
}
}
#end
Related
My app has a table view with a list of jobs it gets from a Parse table. The user can hit the "+" button on the navigation bar to go to another screen to create a new job. Once the new job is created, it returns to the list of jobs with the one that was just created now being added to the list. The problem is that I can select the two jobs that were already in the list with no problems but when I try to select the new job, the app crashes with this error:
Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayI objectAtIndex:]: index 2 beyond bounds [0 .. 1]'
Here is the code for the jobs list:
#import "UnassignedJobs.h"
#import "AppDelegate.h"
#import "NavController.h"
#import "LogInViewController.h"
#import "NewJobViewController.h"
#import "JobDetailViewController.h"
#import <Parse/Parse.h>
#interface UnassignedJobs ()
#property (nonatomic, strong) NSMutableArray *jobs;
#property (nonatomic, strong) NSMutableArray *objectIds;
#property (nonatomic, strong) UIActivityIndicatorView *loadingIndicator;
#property (nonatomic, strong) UIRefreshControl *refresh;
#end
#implementation UnassignedJobs
- (void)viewDidLoad {
[super viewDidLoad];
[self.tableView setDataSource:self];
[self.tableView setDelegate:self];
UIBarButtonItem *logoutButton = [[UIBarButtonItem alloc]initWithTitle:#"Logout" style:UIBarButtonItemStylePlain target:self action:#selector(logoutPressed)];
self.navigationItem.leftBarButtonItem = logoutButton;
UIBarButtonItem *newJobButton = [[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:#selector(createJob)];
self.navigationItem.rightBarButtonItem = newJobButton;
NSString *currentUserFullName = [[PFUser currentUser]objectForKey:#"Name"];
if ([currentUserFullName isEqualToString:#"Cory Pollard"] || [currentUserFullName isEqualToString:#"Richie Ray"]) {
newJobButton.enabled = YES;
}
else {
newJobButton.enabled = NO;
}
CGFloat width = CGRectGetWidth(self.view.bounds);
CGFloat height = CGRectGetHeight(self.view.bounds);
self.loadingIndicator = [[UIActivityIndicatorView alloc]initWithFrame:CGRectMake(width / 2, height / 2, 37, 37)];
self.loadingIndicator.center = CGPointMake(width / 2, height / 2 - 37);
self.loadingIndicator.autoresizingMask = (UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleTopMargin);
self.loadingIndicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyleGray;
self.loadingIndicator.hidesWhenStopped = YES;
[self.view addSubview:self.loadingIndicator];
[self.loadingIndicator startAnimating];
[self getJobs];
self.refresh = [[UIRefreshControl alloc]init];
self.refresh.tintColor = [UIColor blackColor];
[self.refresh addTarget:self action:#selector(refreshData) forControlEvents:UIControlEventValueChanged];
self.refreshControl = self.refresh;
}
- (void)viewDidAppear:(BOOL)animated {
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)getJobs {
self.jobs = [[NSMutableArray alloc]init];
self.objectIds = [[NSMutableArray alloc]init];
PFQuery *query = [PFQuery queryWithClassName:#"Jobs"];
[query setLimit:1000];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
for (NSDictionary *objectDictionary in objects) {
NSString *assigned = [objectDictionary objectForKey:#"assigned"];
if ([assigned isEqualToString:#"no"]) {
[self.jobs addObject:objectDictionary];
// self.objectIds = [objects valueForKeyPath:#"objectId"];
}
for (int i = 0; i < self.jobs.count; i++) {
[self.objectIds addObject:[self.jobs valueForKeyPath:#"objectId"]];
}
}
dispatch_async(dispatch_get_main_queue(), ^ {
[self.tableView reloadData];
[self.loadingIndicator stopAnimating];
});
}
else {
NSLog(#"Error: %# %#", error, [error userInfo]);
}
}];
}
- (void)createJob {
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication]delegate];
NewJobViewController *jobCreateScreen = [[NewJobViewController alloc]initWithNibName:#"NewJobViewController" bundle:nil];
NavController *newJobNav = [[NavController alloc]initWithRootViewController:jobCreateScreen];
newJobNav.delegate = jobCreateScreen;
appDelegate.window.rootViewController = newJobNav;
}
- (void)logoutPressed {
[PFUser logOut];
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication]delegate];
LogInViewController *loginScreen = [[LogInViewController alloc]initWithNibName:#"LogInViewController" bundle:nil];
NavController *loginNavController = [[NavController alloc]initWithRootViewController:loginScreen];
loginNavController.delegate = loginScreen;
appDelegate.window.rootViewController = loginNavController;
}
- (void)refreshData {
[self getJobs];
[self.refresh endRefreshing];
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// Return the number of rows in the section.
return self.jobs.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"UITableViewCell"];
NSDictionary *jobDictionary = [self.jobs objectAtIndex:[indexPath row]];
if (cell == nil) {
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"UITableViewCell"];
}
if (cell) {
cell.backgroundColor = [UIColor clearColor];
cell.textLabel.textColor = [UIColor blackColor];
cell.textLabel.text = [jobDictionary objectForKey:#"job"];
}
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSDictionary *jobDictionary = [self.jobs objectAtIndex:[indexPath row]];
JobDetailViewController *jobDetails = [[JobDetailViewController alloc]initWithNibName:#"JobDetailViewController" bundle:nil];
jobDetails.jobName = [jobDictionary objectForKey:#"job"];
NSDateFormatter *formatter = [[NSDateFormatter alloc]init];
// [formatter setDateFormat:#"MM-dd-yyyy"];
formatter.dateStyle = NSDateFormatterLongStyle;
formatter.timeStyle = NSDateFormatterShortStyle;
jobDetails.jobDate = [formatter stringFromDate:[jobDictionary objectForKey:#"date"]];
jobDetails.objectId = [[self.objectIds objectAtIndex:indexPath.row]objectAtIndex:indexPath.row];
jobDetails.assignedWorker = [jobDictionary objectForKey:#"worker"];
jobDetails.details = [jobDictionary objectForKey:#"details"];
[self.navigationController pushViewController:jobDetails animated:YES];
}
#end
And here is the code to create a new job:
#import "NewJobViewController.h"
#import "AppDelegate.h"
#import "NavController.h"
#import "TabController.h"
#import "UnassignedJobs.h"
#import "AssignedJobs.h"
#import "MyJobs.h"
#import "Users.h"
#import "CompletedJobs.h"
#import <Parse/Parse.h>
#interface NewJobViewController ()
#property (weak, nonatomic) IBOutlet UITextField *jobName;
#property (weak, nonatomic) IBOutlet UITextView *detailTextView;
#property (weak, nonatomic) IBOutlet UIDatePicker *datePicker;
#property (weak, nonatomic) IBOutlet UIButton *createJobButton;
#property (nonatomic, strong) NSDate *jobDate;
#property (nonatomic, strong) NSString *dateString;
#end
#implementation NewJobViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
self.title = #"Create Job";
UIBarButtonItem *cancelButton = [[UIBarButtonItem alloc]initWithTitle:#"Cancel" style:UIBarButtonItemStylePlain target:self action:#selector(cancel)];
self.navigationItem.leftBarButtonItem = cancelButton;
[self.datePicker addTarget:self action:#selector(updateDateString) forControlEvents:UIControlEventValueChanged];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)returnToMainScreen {
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication]delegate];
UnassignedJobs *unassignedJobs = [[UnassignedJobs alloc]initWithNibName:#"UnassignedJobs" bundle:nil];
unassignedJobs.title = #"Unassigned";
NavController *navController = [[NavController alloc]initWithRootViewController:unassignedJobs];
navController.delegate = unassignedJobs;
AssignedJobs *assignedJobs = [[AssignedJobs alloc]initWithNibName:#"AssignedJobs" bundle:nil];
assignedJobs.title = #"Assigned";
NavController *assignedNav = [[NavController alloc]initWithRootViewController:assignedJobs];
assignedNav.delegate = assignedJobs;
CompletedJobs *completed = [[CompletedJobs alloc]initWithNibName:#"CompletedJobs" bundle:nil];
completed.title = #"Completed";
NavController *completedNav = [[NavController alloc]initWithRootViewController:completed];
completedNav.delegate = completed;
MyJobs *myJobs = [[MyJobs alloc]initWithNibName:#"MyJobs" bundle:nil];
myJobs.title = #"My Jobs";
NavController *myNav = [[NavController alloc]initWithRootViewController:myJobs];
myNav.delegate = myJobs;
Users *userList = [[Users alloc]initWithNibName:#"Users" bundle:nil];
userList.title = #"Users";
NavController *userNav = [[NavController alloc]initWithRootViewController:userList];
userNav.delegate = userList;
TabController *tabController = [[TabController alloc]init];
tabController.viewControllers = #[navController, assignedNav, completedNav, myNav, userNav];
appDelegate.window.rootViewController = tabController;
}
- (void)updateDateString {
NSDateFormatter *formatter = [[NSDateFormatter alloc]init];
formatter.dateStyle = NSDateFormatterLongStyle;
formatter.timeStyle = NSDateFormatterMediumStyle;
self.dateString = [formatter stringFromDate:self.datePicker.date];
self.jobDate = [formatter dateFromString:self.dateString];
}
- (IBAction)createJob:(id)sender {
PFObject *job = [PFObject objectWithClassName:#"Jobs"];
job[#"job"] = self.jobName.text;
job[#"details"] = self.detailTextView.text;
job[#"assigned"] = #"no";
job[#"date"] = self.jobDate;
[job saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (succeeded) {
// Close this window and return to unassigned jobs
[self returnToMainScreen];
}
else {
NSString *errorString = [[error userInfo] objectForKey:#"error"];
UIAlertView *errorAlert = [[UIAlertView alloc]initWithTitle:#"Job could not be saved!" message:errorString delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil, nil];
[errorAlert show];
}
}];
}
- (void)cancel {
[self returnToMainScreen];
}
# pragma mark UITextView Delegate Methods
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
if([text isEqualToString:#"\n"]) {
[textView resignFirstResponder];
return NO;
}
return YES;
}
# pragma mark UITextField Delegate Methods
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
[textField resignFirstResponder];
return NO;
}
#end
you have to reload the jobs data source, and show the table again,
so,
#implementation UnassignedJobs ...
- (void)viewDidAppear:(BOOL)animated {
[self getJobs];
[self.table reloadData];
}
and dont forget to clean the mutable arrays
- (void)getJobs {
[self.jobs removeAllObjects];
[self.objectIds removeAllObjects];
self.jobs = [[NSMutableArray alloc]init];
self.objectIds = [[NSMutableArray alloc]init];
...}
MaKo is correct in stating you need to reload the jobs data source (because after you pop back to the table controller, the data source won't know about the new row until it is reloaded. However, you don't need to re-initiate the arrays (even if you did this, you wouldn't need to remove all the objects first since it's redundant). It would be better practice to maintain your existing arrays properly than to create new ones each time.
I've tried using ASyncImageView for this purpose, but I'm a bit confused as to how I'd implement it for my specific case. I currently have a MatchCenterViewController that contains a table inside of it. It's loading the images for the cells synchronously, which is causing a lot of lag when scrolling through the table. How can I modify the way I'm loading the remote images so that it's done asynchronously? My code is below:
#import "MatchCenterViewController.h"
#import <UIKit/UIKit.h>
#import "MatchCenterCell.h"
#interface MatchCenterViewController () <UITableViewDataSource, UITableViewDelegate>
#property (nonatomic, strong) UITableView *matchCenter;
#property (nonatomic, assign) BOOL matchCenterDone;
#property (nonatomic, assign) BOOL hasPressedShowMoreButton;
#end
#implementation MatchCenterViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
_matchCenterDone = NO;
_hasPressedShowMoreButton = NO;
// Set up MatchCenter table
self.matchCenter = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewCellStyleSubtitle];
self.matchCenter.frame = CGRectMake(0,70,320,self.view.frame.size.height-100);
self.edgesForExtendedLayout = UIRectEdgeAll;
self.matchCenter.contentInset = UIEdgeInsetsMake(0.0f, 0.0f, CGRectGetHeight(self.tabBarController.tabBar.frame), 0.0f);
_matchCenter.dataSource = self;
_matchCenter.delegate = self;
[self.view addSubview:self.matchCenter];
self.expandedSection = -1;
_matchCenterArray = [[NSArray alloc] init];
// Refresh button
UIImageView *refreshImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"refresh.png"]];
refreshImageView.frame = CGRectMake(280, 30, 30, 30);
refreshImageView.userInteractionEnabled = YES;
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(refreshPressed:)];
[refreshImageView addGestureRecognizer:tapGesture];
[self.view addSubview:refreshImageView];
// Preparing for MC and indicating loading
self.matchCenterArray = [[NSArray alloc] init];
UIActivityIndicatorView *activityIndicator = [[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
activityIndicator.center = CGPointMake(self.view.frame.size.width / 2.0, self.view.frame.size.height / 2.0);
[self.view addSubview: activityIndicator];
[activityIndicator startAnimating];
_matchCenterDone = NO;
// Disable ability to scroll until table is MatchCenter table is done loading
self.matchCenter.scrollEnabled = NO;
[PFCloud callFunctionInBackground:#"MatchCenter3"
withParameters:#{}
block:^(NSArray *result, NSError *error) {
if (!error) {
_matchCenterArray = result;
[activityIndicator stopAnimating];
[_matchCenter reloadData];
_matchCenterDone = YES;
self.matchCenter.scrollEnabled = YES;
NSLog(#"Result: '%#'", result);
}
}];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return _matchCenterArray.count;
}
//the part where i setup sections and the deleting of said sections
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
return 21.0f;
}
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section
{
return 40;
}
- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section {
//code snipped out for conciseness
}
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
//Header code snipped out for conciseness
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSDictionary *currentSectionDictionary = _matchCenterArray[section];
NSArray *top3ArrayForSection = currentSectionDictionary[#"Top 3"];
return (top3ArrayForSection.count-1 < 1) ? 1 : top3ArrayForSection.count-1;
}
// Cell layout
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Initialize cell
static NSString *CellIdentifier = #"MatchCenterCell";
MatchCenterCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (!cell) {
// if no cell could be dequeued create a new one
cell = [[MatchCenterCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
//[cell.contentView addSubview:cell.priceLabel];
[cell.contentView addSubview:cell.conditionLabel];
// No cell seperators = clean design
tableView.separatorColor = [UIColor clearColor];
NSDictionary *currentSectionDictionary = _matchCenterArray[indexPath.section];
NSArray *top3ArrayForSection = currentSectionDictionary[#"Top 3"];
if (top3ArrayForSection.count-1 < 1) {
// title of the item
cell.textLabel.text = #"No items found, but we'll keep a lookout for you!";
cell.textLabel.font = [UIFont systemFontOfSize:12];
}
else {
// title of the item
cell.textLabel.text = _matchCenterArray[indexPath.section][#"Top 3"][indexPath.row+1][#"Title"];
cell.textLabel.font = [UIFont systemFontOfSize:14];
// price + condition of the item
NSString *price = [NSString stringWithFormat:#"$%#", _matchCenterArray[indexPath.section][#"Top 3"][indexPath.row+1][#"Price"]];
NSString *condition = [NSString stringWithFormat:#"%#", _matchCenterArray[indexPath.section][#"Top 3"][indexPath.row+1][#"Item Condition"]];
cell.detailTextLabel.text = [NSString stringWithFormat:#"%# - %#", price, condition];
cell.detailTextLabel.textColor = [UIColor colorWithRed:0/255.0f green:127/255.0f blue:31/255.0f alpha:1.0f];
// image of the item
NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:_matchCenterArray[indexPath.section][#"Top 3"][indexPath.row+1][#"Image URL"]]];
[[cell imageView] setImage:[UIImage imageWithData:imageData]];
cell.imageView.layer.masksToBounds = YES;
cell.imageView.layer.cornerRadius = 2.5;
}
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.section == self.expandedSection || indexPath.row <= 3) {
return 65;
}
return 0;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (_matchCenterDone == YES) {
self.itemURL = _matchCenterArray[indexPath.section][#"Top 3"][indexPath.row+1][#"Item URL"];
[self performSegueWithIdentifier:#"WebViewSegue" sender:self];
}
}
#end
#implementation MoreButton
#end
// Use background thread to avoid the laggy tableView
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
// Download or get images here
NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:#"url"]];
UIImage *cellImage = [[UIImage alloc] initWithData:imageData];
// Use main thread to update the view. View changes are always handled through main thread
dispatch_async(dispatch_get_main_queue(), ^{
// Refresh image view here
[cell.imageView setImage:cellImage];
[cell.imageView.layer setMasksToBounds:YES];
[cell.imageView.layer setCornerRadius:2.5f];
[cell setNeedsLayout];
});
});
The most common solution to this is AFNetworking's AFImageView. It handles this situation perfectly. It should take you no time at all to implement, so give it a go.
Guy Kogus' answer works great. He's right, I got into all kinds of issues like he mentions in the comment above, doing similar things like the first answer.
Still, here's an example on how to use AFNetworking's UIImageView category. Assuming the code below is in a Cell (or something inheriting from a UIView).
First import the class:
#import "UIImageView+AFNetworking.h"
Then add this code in your UITableViewCell:
NSString *url = #"http://www.domain.www/some_image.jpg";
[self.productImage setImageWithURL:[NSURL URLWithString:url]
placeholderImage:[UIImage imageNamed:#"placeholderImg.png"]];
[self setNeedsLayout];
Not 100% sure if setNeedsLayout is necessary in this case. Feel free to correct this.
I'm trying to load a table with content from Twitter. The table is in a UIView and being created in the drawRect()...but I keep getting a warning:
Property access result unused - getters should not be used for side effects
on each.
Nothing show up in my table.
Here's my .h file:
#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>
#import <Twitter/Twitter.h>
#import "ColorController.h"
#interface TwitterController : UIView <UITableViewDelegate, UITableViewDataSource> {
UIButton* btnCloseView;
UITableView* tblTweets;
UIImageView* imgTwitterIcon;
ColorController* colorManager;
NSMutableArray* tweetsArray;
NSString* twitterID;
}
#property (nonatomic, retain) NSString* twitterID;
- (void) getTweets;
- (void) closeWin;
#end
and my .m
#import "TwitterController.h"
#implementation TwitterController
#synthesize twitterID;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
colorManager = [ColorController new];
}
return self;
}
- (void)drawRect:(CGRect)rect {
imgTwitterIcon = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"imgTwitterBird"]];
CGRect twitterIconFrame = [imgTwitterIcon frame];
twitterIconFrame.origin.x = 50.0;
twitterIconFrame.origin.y = 20.0;
tblTweets = [[UITableView alloc] initWithFrame:CGRectMake(50.0, 25.0, 220.0, 500.0)];
tblTweets.separatorStyle = UITableViewCellSeparatorStyleSingleLine;
tblTweets.separatorColor = [colorManager setColor:176.0:196.0:222.0];
tblTweets.layer.borderWidth = 1.0;
tblTweets.rowHeight = 20.0;
tblTweets.scrollEnabled = YES;
tblTweets.delegate.self;
tblTweets.dataSource.self;
UIImage* imgCloseButton = [UIImage imageNamed:#"btnCloseWindow.png"];
CGSize imageSize = imgCloseButton.size;
btnCloseView = [[UIButton alloc] initWithFrame: CGRectMake(220.0, 550.0, imageSize.width, imageSize.height)];
[btnCloseView setImage:[UIImage imageNamed:#"btnCloseWindow.png"] forState:UIControlStateNormal];
[btnCloseView addTarget:self action:#selector(closeWin:) forControlEvents:UIControlEventTouchUpInside];
[self getTweets];
[self addSubview:tblTweets];
[self addSubview:imgTwitterIcon];
[self addSubview:btnCloseView];
}
- (void) getTweets {
//array to hold tweets
tweetsArray = [[NSMutableArray alloc] init];
///set up a NSURL to the twitter API
NSURL* twitterAPI = [NSURL URLWithString:[NSString stringWithFormat:#"https://api.twitter.com/1/statuses/user_timeline.json?include_entities=true&include_rts=true&screen_name=%#&count=10", twitterID]];
//get last 10 tweets (max is 20)
TWRequest *twitterRequest = [[TWRequest alloc] initWithURL:twitterAPI
parameters:nil requestMethod:TWRequestMethodGET];
// Notice this is a block, it is the handler to process the response
[twitterRequest performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error) {
if ([urlResponse statusCode] == 200) {
// The response from Twitter is in JSON format
// Move the response into a dictionary and print
NSError *error;
NSDictionary *tweetsDict = [NSJSONSerialization JSONObjectWithData:responseData options:0 error:&error];
for(NSDictionary* thisTweetDict in tweetsDict) {
[tweetsArray addObject:[thisTweetDict objectForKey:#"text"]];
}
[tblTweets reloadData];
}
else
NSLog(#"Twitter error, HTTP response: %i", [urlResponse statusCode]);
}];
}
#pragma mark Table Management
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return [tweetsArray count];
NSLog(#"%i", [tweetsArray count]);
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [tweetsArray count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"tableCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
cell.textLabel.textColor = [UIColor colorWithRed:66.0/255.0 green:66.0/255.0 blue:66.0/255.0 alpha:1];
cell.textLabel.font = [UIFont fontWithName:#"Helvetica-Bold" size: 13.0];
cell.textLabel.text = [tweetsArray objectAtIndex:indexPath.row];
CGRect cellFrame = [cell frame];
cellFrame.size.height = 25.0;
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSString* thisTweet = [tweetsArray objectAtIndex:indexPath.row];
}
#pragma mark Close Window
- (void) closeWin {
NSMutableDictionary* userData = [[NSMutableDictionary alloc] init];
[userData setObject:#"closeTwitter" forKey:#"theEvent"];
[[NSNotificationCenter defaultCenter] postNotificationName:#"theMessenger" object:self userInfo: userData];
}
#end
drawRect is used to draw stuff inside this views, using drawing functions
You should move your views additions to the layoutSubviews
Instead of - (void)drawRect:(CGRect)rect use - (void)layoutSubviews
This may or may not solve your issues, but nevertheless its the correct approach
I have a tableview to load news from internet. I and trying to nil all properties in viewDidUnload.
- (void)viewDidUnload
{
self.newsArray = nil;
self.newsTableView = nil;
self.indicatorView = nil;
// self.iconDownLoader = nil;
self.downloadArray = nil;
[super viewDidUnload];
}
Every time the app crash in viewDidUnload. If I comment self.iconDownLoader = nil;, it will be fine. So can any one tell me why does this happen? Thanks you.
---------------------NewsViewController.m--------------------------
//
// NewsViewController.m
//
// Created by on 18/01/12.
// Copyright (c) 2012 __MyCompanyName__. All rights reserved.
//
#import "NewsViewController.h"
#import "ASIHTTPRequest.h"
#import "SBJson.h"
#import "NewsModel.h"
#import "NewsDetailViewController.h"
#define kCustomRowCount 6
#define IconPlaceHolder #"Spinner"
#implementation NewsViewController
#synthesize appDelegate, newsTableViewCell, newsTableView, indicatorView;
#synthesize iconDownLoader, newsArray, downloadArray;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// setup appDelegate
self.appDelegate = (SydneyAppDelegate *)[[UIApplication sharedApplication] delegate];
// initial arrays
self.newsArray = [[NSMutableArray alloc] init];
self.downloadArray = [[NSMutableArray alloc] init];
}
return self;
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}
#pragma mark - View lifecycle
- (void)viewDidLoad
{
[super viewDidLoad];
if(self.appDelegate.reachable) {
[self getNews];
}
else
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"No Connection" message:#"No Internet connection. Please try again later." delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alert show];
}
}
- (void)viewDidUnload
{
self.newsArray = nil;
self.newsTableView = nil;
self.indicatorView = nil;
// self.iconDownLoader = nil;
self.downloadArray = nil;
[super viewDidUnload];
}
#pragma mark - ASIHTTPRequest
- (void) getNews
{
NSURL *url = [NSURL URLWithString:#"http://ferrarimaseratisydney.com/api/getPublicNews.html"];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setDelegate:self];
[request startAsynchronous];
}
- (void) requestFinished:(ASIHTTPRequest *)request
{
NSString *responseString = [request responseString];
NSArray *json = [responseString JSONValue];
for (id aNewsInJson in json)
{
NewsModel *aNews = [[NewsModel alloc] initWithJson:aNewsInJson];
[self.newsArray addObject:aNews];
}
[self.indicatorView removeFromSuperview];
[self.newsTableView reloadData];
}
- (void) requestFailed:(ASIHTTPRequest *)request
{
NSError *error;
error = [request error];
}
#pragma mark - Table view delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
// Navigation logic may go here. Create and push another view controller.
NewsDetailViewController *newsDetailViewController = [[NewsDetailViewController alloc] init];
// transform news array
newsDetailViewController.news = [self.newsArray objectAtIndex:indexPath.row];
// Pass the selected object to the new view controller.
[self.navigationController pushViewController:newsDetailViewController animated:YES];
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [self.newsArray count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"NewsCellIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
[[NSBundle mainBundle] loadNibNamed:#"NewsTableViewCell" owner:self options:nil];
cell = self.newsTableViewCell;
self.newsTableViewCell = nil;
}
// read from newsModel
NewsModel *news = [self.newsArray objectAtIndex:indexPath.row];
UILabel *label;
label = (UILabel *)[cell viewWithTag:10];
label.text = [NSString stringWithString:news.title];
label = nil;
label = (UILabel *)[cell viewWithTag:11];
label.text = [NSString stringWithString:news.description];
UIImageView *imageView = (UIImageView *)[cell viewWithTag:12];
imageView.image = news.image;
if (news.image == nil)
{
imageView.image = [UIImage imageNamed:IconPlaceHolder];
self.iconDownLoader = [[IconDownLoader alloc] init];
self.iconDownLoader.url = news.imageUrl;
self.iconDownLoader.delegate = self;
self.iconDownLoader.indexPath = indexPath;
if (self.appDelegate.ip4 == YES)
{
self.iconDownLoader.width = 300;
self.iconDownLoader.height = 150;
}
else
{
self.iconDownLoader.width = 150;
self.iconDownLoader.height = 75;
}
[self.downloadArray addObject:self.iconDownLoader];
[self.iconDownLoader start];
}
return cell;
}
#pragma mark - IconDownLoaderDelegate
- (void)iconDownLoadFinsh:(NSData *)imageData row:(NSIndexPath *)indexPath {
UITableViewCell *cell = [self.newsTableView cellForRowAtIndexPath:indexPath];
UIImageView *imageView = (UIImageView *)[cell viewWithTag:12];
if (imageData != 0)
{
imageView.image = [UIImage imageWithData:imageData];
}
else
{
imageView.image = [UIImage imageNamed:#"icon57"];
}
NewsModel *newsModel = [self.newsArray objectAtIndex:indexPath.row];
newsModel.image = [UIImage imageWithData:imageData];
}
#end
-----------------------IconDownLoader.m-------------------
//
// IconDownLoader.m
//
// Created by on 24/11/11.
// Copyright (c) 2011 __MyCompanyName__. All rights reserved.
//
#import "IconDownLoader.h"
#import "ASIHTTPRequest.h"
#implementation IconDownLoader
#synthesize delegate = _delegate;
#synthesize url = _url;
#synthesize indexPath = _indexPath;
#synthesize width = _width;
#synthesize height = _height;
#synthesize request = _request;
- (void)start {
NSString *originalString = #"width=%s&height=%s";
NSString *newString = [NSString stringWithFormat:#"width=%d&height=%d&type=jpg", self.width, self.height];
NSString *resizedURL = [self.url stringByReplacingOccurrencesOfString:originalString withString:newString];
NSURL *url = [NSURL URLWithString:[resizedURL stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
_request = [ASIHTTPRequest requestWithURL:url];
if (_indexPath) {
_request.userInfo = [NSDictionary dictionaryWithObject:_indexPath forKey:#"indexPath"];
}
[_request setDelegate:self];
[_request startAsynchronous];
}
- (void)requestFinished:(ASIHTTPRequest *)request {
NSInteger statusCode = request.responseStatusCode;
switch (statusCode) {
case 401: // Not Authorized: either you need to provide authentication credentials, or the credentials provided aren't valid.
break;
case 200: {
NSData *responseData = [request responseData];
if (!responseData) {
UIAlertView *alertView;
alertView = [[UIAlertView alloc] initWithTitle:#"Oops" message:[NSString stringWithFormat:#"Download failed in row %d", _indexPath.row] delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
return;
}
[_delegate iconDownLoadFinsh:responseData row:[request.userInfo objectForKey:#"indexPath"]];
}
break;
default:{
}
}
}
- (void)dealloc {
if (_request != nil) {
[_request clearDelegatesAndCancel];
}
}
#end
Generally in viewDidUnload you should only release and zero out all references to the nib objects you own.
That said, you can destroy model objects in viewDidUnload too if they consume a lot of memory. You should remember that viewDidUnload is a counterpart to viewDidLoad, so a good rule of thumb is to destroy only those objects in viewDidUnload which you create in viewDidLoad. You should also remember that viewDidUnload is not called when the view controller is released – only when its view is.
In your case I would not release newsArray and downloadArray just because you create them in init.... I would just send them removeAllObjects instead.
As for the crash, you create a new shared icon downloader every time a cell needs an image, which is a bit awkward. If you do need a shared downloader instance, you should not recreate it for each and every cell. It looks like you want to keep all the downloaders you created in an ivar array instead because each downloader is one-shot and is responsible for loading a single image.
Not enough information here to tell, but probably you have released iconDownloader directly in some other part of the code we cannot see without setting the reference to nil at that time.
Then in viewDidUnload you are trying to release an invalid reference.
For #synthesize, use:
#synthesize iconDownLoader = _iconDownloader;
then fix all of the compiler warnings to use self.icondownloder instead of iconDownloader, and eliminate all uses of "release" (assuming your property is marked as retain which it should be).
In fact, perhaps your whole problem is that the property is not a retain property so you make the iconDOwnloader and it's promptly released.
My app is want to get the albums list of the iphone and all the photos in certain album.
In the app I enumerate the photos in one album of the iphone.
As there may be lots of photos of certain album, considering of performance I use GCD:dispatch_async. But it always crashes when the tableview cell updates which is invoked by KVO.
I've no idea about whether I use KVO or GCD in a wrong way.
Now alternatively I use performSelectorInBackground: replacing of dispatch_async. Now the app is not crashed but the app's performance is poor: the title of the cell will only be shown when you touch on it or scroll the tableview when there are many photos. In other words, the main thread must be blocked.
Attached is the code and the core code is in AlbumListViewController.m.
Can any one help me to check it ?
I just want to know:
1 why the app is crashed if using dispatch_async
2 how can I improve the performance in case of many photos.
thanks.
Below is my Code:
//
// RootViewController.h
// AlbumDemo
#import
#interface RootViewController : UITableViewController {
NSMutableArray *_listArray;
}
#property (nonatomic, retain) NSMutableArray *listArray;
#end
// RootViewController.m
#import "RootViewController.h"
#import
#import "AlbumListViewController.h"
NSString *thumnail = #"thumnail";
NSString *albumName = #"albumName";
NSString *albumNum = #"albumNum";
NSString *albumGroup = #"albumGroup";
#implementation RootViewController
#synthesize listArray = _listArray;
#pragma -
#pragma Function
- (void)setUp
{
_listArray = [[NSMutableArray alloc] initWithCapacity:1];
self.title = #"Albums";
}
- (void)fetchAlbumList
{
ALAssetsLibrary *assetLib = [[[ALAssetsLibrary alloc] init] autorelease];
ALAssetsFilter *fileter = [ALAssetsFilter allPhotos];
[assetLib enumerateGroupsWithTypes:ALAssetsGroupAll
usingBlock:^(ALAssetsGroup *group, BOOL *stop)
{
if (group)
{
[group setAssetsFilter:fileter];
NSString *_groupName = [group valueForProperty:ALAssetsGroupPropertyName];
NSNumber *_groupNum = [NSNumber numberWithInteger:[group numberOfAssets]];
UIImage *_groupImage = [UIImage imageWithCGImage:[group posterImage]];
NSDictionary *dic = [NSDictionary dictionaryWithObjectsAndKeys:_groupName,albumName,_groupNum,albumNum,_groupImage,thumnail,group,albumGroup, nil];
[_listArray addObject:dic];
[self.tableView reloadData];
}
else
{
NSLog(#"_listArray :%#",_listArray);
}
}
failureBlock:^(NSError *error)
{
NSLog(#"Error: %#", error);;
}
];
}
#pragma -
#pragma ViewController lift cycle
- (void)viewDidLoad
{
[super viewDidLoad];
[self setUp];
[self fetchAlbumList];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return 50;
}
// Customize the number of sections in the table view.
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [_listArray count];
}
// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UILabel *nameLab = nil;
UILabel *numLab = nil;
UIImageView *thumnailImage = nil;
UIFont *font = [UIFont boldSystemFontOfSize:18];
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
thumnailImage = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0,50, 50)];
thumnailImage.tag = 100;
[cell.contentView addSubview:thumnailImage];
[thumnailImage release];
nameLab = [[UILabel alloc] initWithFrame:CGRectMake(60, 10, 100, 30)];
nameLab.tag = 200;
nameLab.backgroundColor = [UIColor clearColor];
nameLab.font = font;
[cell.contentView addSubview:nameLab];
[nameLab release];
numLab = [[UILabel alloc] initWithFrame:CGRectMake(200, 10, 50, 30)];
numLab.tag = 300;
numLab.backgroundColor = [UIColor clearColor];
numLab.textColor = [UIColor grayColor];
numLab.font = font;
[cell.contentView addSubview:numLab];
[numLab release];
}
else
{
thumnailImage = (UIImageView *)[cell.contentView viewWithTag:100];
nameLab = (UILabel *)[cell.contentView viewWithTag:200];
numLab = (UILabel *)[cell.contentView viewWithTag:300];
}
NSDictionary *dic = [self.listArray objectAtIndex:indexPath.row];
thumnailImage.image = (UIImage *)[dic valueForKey:thumnail];
NSString *title = [dic valueForKey:albumName];
CGSize titleSize = [title sizeWithFont:font];
CGRect rect = nameLab.frame;
rect.size = titleSize;
nameLab.frame = rect;
nameLab.text = title;
rect = numLab.frame;
rect.origin.x = 60 + nameLab.frame.size.width + 10;
numLab.frame = rect;
numLab.text = [NSString stringWithFormat:#"(%d)",[[dic valueForKey:albumNum] intValue]];
// Configure the cell.
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSDictionary *dic = [self.listArray objectAtIndex:indexPath.row];
AlbumListViewController *viewController = [[AlbumListViewController alloc] initWithAssetGroup:[dic valueForKey:albumGroup]];
[self.navigationController pushViewController:viewController animated:YES];
[viewController release];
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
- (void)didReceiveMemoryWarning
{
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Relinquish ownership any cached data, images, etc that aren't in use.
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Relinquish ownership of anything that can be recreated in viewDidLoad or on demand.
// For example: self.myOutlet = nil;
}
- (void)dealloc
{
My_Release (_listArray);
[super dealloc];
}
#end
// AlbumListViewController.h
// AlbumDemo
#import
#import
#interface AlbumListViewController : UITableViewController {
NSMutableArray *_marr;
ALAssetsGroup *_assetsGroup;
}
#property (nonatomic, retain) NSMutableArray *list;
#property (nonatomic, retain) ALAssetsGroup *assetsGroup;
- (id)initWithAssetGroup:(ALAssetsGroup *)group;
#end
// AlbumListViewController.m
// AlbumDemo
#import "AlbumListViewController.h"
#interface PhotoObj : NSObject {
NSString *_name;
UIImage *_thumbnail;
UIImage *_fullImage;
}
#property (nonatomic, copy ) NSString *name;
#property (nonatomic, retain) UIImage *thumbnail;
#property (nonatomic, retain) UIImage *fullImage;
#end
#implementation PhotoObj
#synthesize name = _name;
#synthesize thumbnail = _thumbnail,fullImage = _fullImage;
- (void)dealloc
{
My_Release(_thumbnail);
My_Release(_fullImage);
My_Release(_name);
[super dealloc];
}
#end
#interface AlbumListViewController()
- (NSMutableArray*)list;
- (NSUInteger)countOfList;
- (id)objectInListAtIndex:(NSUInteger)idx;
- (void)insertObject:(id)anObject inListAtIndex:(NSUInteger)idx;
- (id)objectInListAtIndex:(NSUInteger)idx;
- (void)removeObjectFromListAtIndex:(NSUInteger)idx;
- (void)replaceObjectInListAtIndex:(NSUInteger)idx withObject:(id)anObject;
- (void)setList:(NSMutableArray *)_arr;
#end
#implementation AlbumListViewController
#synthesize assetsGroup = _assetsGroup;
- (id)initWithAssetGroup:(ALAssetsGroup *)group
{
self = [self initWithStyle:UITableViewStylePlain];
if (self )
{
_marr = [[NSMutableArray alloc] initWithCapacity:1];
self.assetsGroup = group;
self.tableView.delegate = self;
self.tableView.dataSource = self;
}
return self;
}
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
// Custom initialization
}
return self;
}
- (void)dealloc
{
My_Release(_marr);
My_Release(_assetsGroup);
[self removeObserver:self forKeyPath:#"list"];
[super dealloc];
}
- (void)didReceiveMemoryWarning
{
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
}
#pragma mark - View lifecycle
- (void)parseAssetGroup
{
[_marr removeAllObjects];
[self.assetsGroup enumerateAssetsUsingBlock:^(ALAsset *result, NSUInteger index, BOOL *stop) {
if (result)
{
PhotoObj *obj = [[PhotoObj alloc] init];
obj.thumbnail = [UIImage imageWithCGImage:[result thumbnail]];
ALAssetRepresentation *represention = [result defaultRepresentation];
obj.fullImage = [UIImage imageWithCGImage:[represention fullScreenImage]];
obj.name = [[represention url] absoluteString];
[self willChangeValueForKey:#"list"];
[self insertObject:obj inListAtIndex:[_marr count]];
[self didChangeValueForKey:#"list"];
My_Release(obj);
}
}];
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self addObserver:self forKeyPath:#"list" options:NSKeyValueObservingOptionNew |NSKeyValueObservingOptionOld context:NULL];
/*
if performSelectorInBackground, the perofrmance is poor
as the title of the cell will be shown in a long time and it now seems the main thread is blocked
*/
[self performSelectorInBackground:#selector(parseAssetGroup) withObject:nil];
/*
using dispatch_async it always crashes
as it says the sth is wrong with the tableview update
*/
// dispatch_async(dispatch_get_main_queue(), ^{
// [self parseAssetGroup];
// });
}
- (void)viewDidUnload
{
[super viewDidUnload];
}
#pragma mark - Table view data source
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return 50;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return [_marr count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UIImageView *thumbNail = nil;
UILabel *nameLab = nil;
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
thumbNail = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 50, 50)];
thumbNail.tag = 99;
[cell.contentView addSubview:thumbNail];
[thumbNail release];
nameLab = [[UILabel alloc] initWithFrame:CGRectMake(60, 10, 240, 40)];
nameLab.numberOfLines = 2;
nameLab.font = [UIFont systemFontOfSize:16];
nameLab.tag = 199;
[cell.contentView addSubview:nameLab];
[nameLab release];
}
else
{
thumbNail = (UIImageView *)[cell.contentView viewWithTag:99];
nameLab = (UILabel *)[cell.contentView viewWithTag:199];
}
// Configure the cell...
PhotoObj *obj = [_marr objectAtIndex:indexPath.row];
nameLab.text = obj.name;
thumbNail.image = obj.thumbnail;
return cell;
}
#pragma mark -
- (NSUInteger)countOfList
{
return [_marr count];
}
- (NSMutableArray*)list
{
return _marr;
}
- (void)setList:(NSMutableArray *)_arr
{
if (_marr != _arr)
{
[_marr release];
_marr = _arr;
}
}
- (id)objectInListAtIndex:(NSUInteger)idx
{
return [_marr objectAtIndex:idx];
}
- (void)insertObject:(id)anObject inListAtIndex:(NSUInteger)idx
{
if ([NSThread isMainThread])
{
NSLog(#"insert main thread");
}
else
{
NSLog(#"insert not main thread");
}
[_marr insertObject:anObject atIndex:idx];
}
- (void)removeObjectFromListAtIndex:(NSUInteger)idx
{
[_marr removeObjectAtIndex:idx];
}
- (void)replaceObjectInListAtIndex:(NSUInteger)idx withObject:(id)anObject
{
[_marr replaceObjectAtIndex:idx withObject:anObject];
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
NSIndexSet *indices = [change objectForKey:NSKeyValueChangeIndexesKey];
if (indices == nil)
return; // Nothing to do
// Build index paths from index sets
NSUInteger indexCount = [indices count];
NSUInteger buffer[indexCount];
[indices getIndexes:buffer maxCount:indexCount inIndexRange:nil];
NSMutableArray *indexPathArray = [NSMutableArray array];
for (int i = 0; i
I ran into exactly the same problem today. In short, the reason is you cannot do UIKit related tasks, like updating a table, or in my case a Textview, from the background dispatch queue. Check the link below for more details.
comparison GCD vs. performSelectorInBackground: dispatch_async not in background
A possible solution is the following: instead of assigning your fresh data in your update block directly to the KVO variable which causes the crash, you dispatch another block which does this to the main queue, from inside your update block. If you use the dispatch_async_f function to do this, you can pass a pointer to your data as context.
Like this:
dispatch_async(yourQueue, ^() {
NSArray *data;
// do stuff to alloc and fill the array
// ...
dispatch_async(dispatch_get_main_queue(), ^() {
myObj.data = data; // the assignment, which triggers the KVO.
});
});
For me this works without retaining and releasing the data. Not sure, if this correct.