I have constructed the following method fetchLastFiveLogins and the method successfully adds the users avatar to the _avatarButton. However, when a user taps the avatar it calls the fillUserName method. That part is working, but I can't figure out how to fill the _textfieldUsername with the username associated with the avatar. Below is a snippet of the fetchLastFiveLogins method.
// sort / filter "results" to display the last five "lastLogin(s)"
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"lastLogin" ascending:NO];
NSArray *sortDescriptors = [NSArray arrayWithObjects:sortDescriptor, nil];
[request setFetchLimit:5];
[request setSortDescriptors:sortDescriptors];
// fetch records and handle error
NSError *error;
NSArray *results = [_managedObjectContext executeFetchRequest:request error:&error];
if (!results) {
// handle error
// also if there is login data handle error so app doesn't crash
}
Account *anAccount;
// create a stock image
UIImage *btnImage = [UIImage imageNamed:#"HomeBrewPoster1.jpg"];
NSMutableArray *avatars = [NSMutableArray arrayWithCapacity:5];
for ( anAccount in results) {
NSLog(#"results%#",anAccount.lastLogin);
if(anAccount.lastLogin) {
NSLog(#"anAccount.lastLogin = %#, by:%#",anAccount.lastLogin,anAccount.username);
if (anAccount.avatar != nil) {
UIImage *avatarImg = [UIImage imageWithData:anAccount.avatar ];
[avatars addObject:avatarImg];
}
else {
[avatars addObject:btnImage];
}
}
}
NSLog(#"avatars array%lu",(unsigned long)avatars.count);
for ( NSInteger i = 0; i < 4; i++) {
// Check that we have enough logins
if (i < results.count) {
NSLog(#"avatars =%#",avatars[i]);
CGFloat staticX = 0;
CGFloat staticWidth = 80;
CGFloat staticHeight = 80;
CGFloat staticPadding = 5;
_avatarButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
// the last two values control the size of the button
_avatarButton.frame = CGRectMake(0, 0, 80, 80);
[_avatarButton setFrame:CGRectMake((staticX + (i * (staticHeight + staticPadding))),5,staticWidth,staticHeight)];
// make corners round
_avatarButton.layer.cornerRadius = 40; // value varies -- // 35 yields a pretty good circle.
_avatarButton.clipsToBounds = YES;
// assign method / action to button
[_avatarButton addTarget:self action:#selector(fillUserName) forControlEvents:UIControlEventTouchDown];
[_avatarButton setBackgroundImage:[avatars objectAtIndex:i] forState:UIControlStateNormal];
NSLog(#"avatarImage = %#",[_avatarButton backgroundImageForState:UIControlStateNormal]);
[_avatarScroll addSubview:_avatarButton];
}
}
}
Step 1: Make the fillUserName method take the parameter fillUserName:(id) sender, so that the method will be passed the button that was pressed. You'll want to tweak the addTarget so that it uses #selector(fillUserName:) (with the colon at the end).
Step 2: You'll need to be able to distinguish which of the buttons was pressed. Often people use the tag property of UIButton (and other controls) to distinguish the buttons. If you set the tag to be the array index of the result, then the button with tag 0 is the 0th entry in your results array. See this question: How do i set and get UIButtons' tag? for some code samples.
Step 3: Ensure that you've held on to references to the results array, so that you can access it from the fillUserName: method.
Update: As I mention in my comment, below, a solution that makes use of blocks seems like a much better solution, and some folks have written some nice code to enable that.
Related
In my table cells, there is a button that displays a drop-down menu when you click on it. The problem is, the drop-down menu exceeds the height of the cell and bleeds into the cell below.
In that case, if I click the item in the drop-down, it actually acts as I clicked the cell below.
I tried using the code below but it doesn't seem to work.
[super bringSubviewToFront:dropDown];
[super willMoveToSuperview:dropDown];
The function that shows the drop-down is defined in the custom class for the table cell:
- (IBAction)eventAction:(id)sender {
NSArray * arr = [[NSArray alloc] init];
arr = [NSArray arrayWithObjects:#"Hello 0", #"Hello 1",nil];
NSArray * arrImage = [[NSArray alloc] init];
arrImage = [NSArray arrayWithObjects:[UIImage imageNamed:#"apple.png"], [UIImage imageNamed:#"apple2.png"], nil];
if(dropDown == nil) {
CGFloat f = 100;
dropDown = [[NIDropDown alloc]showDropDown:sender :&f :arr :arrImage :#"down"];
dropDown.delegate = self;
dropDown.layer.zPosition = 1;
[super bringSubviewToFront:dropDown];
[super willMoveToSuperview:dropDown];
}else {
[dropDown hideDropDown:sender];
[self rel];
}
}
Any help is greatly appreciated!!
Thank you!!
Rather than being a case of views being on top of each other, I think it might be that the touch is being interpreted by the table cell below as well.
Try disabling user interaction in the table view when the eventAction is fired.
tableView.userInteractionEnabled = NO;
And then turn it back on when the drop down view is dismissed.
I'm working on an iPhone game and currently the game uses many UIImageViews to show space ships, bullets and other things. The game probably has 30-40 different subviews in the view controller and I've decided that I should probably programmatically add subviews when they are needed rather than starting the game with them in the hidden state and making them unhidden when needed (for performance issues) so here are my questions:
(1) will there be a significant performance gain from adding subviews when needed?
(2) how do I release a subview when ARC is enabled? Im trying to add the views (based on type) into an NSMutableArray when its created to hold it, and removing it from the array when I'm done using it, does removing it from the array deallocate that memory?
heres the code (theres an NSTimer that runs the movement method every 0.03 seconds, and the get and set methods are on an "instance variable" NSMutableArray);
-(void)movement {
if (shootButton3.isHighlighted) {
NSMutableArray *array = [self getBulletArray];
[self setarray:array];
}
[self shootBullet: [self getArray]];
}
-(NSMutableArray *)getBulletArray {
UIImageView *bullet = [[UIImageView alloc] initWithFrame:CGRectMake(ship.center.x + 20, ship.center.y, 15, 3)];
bullet.image = [UIImage imageNamed:#"bullet2.png"];
bullet.hidden = NO;
[self.view addSubview:bullet];
NSMutableArray *array = [[NSMutableArray alloc] init];
[array addObject:bullet];
return array;
}
-(void)shootBullet: (NSMutableArray *)bulletArray {
for (int i = 0; i < [bulletArray count]; i++) {
UIImageView *bullet = [bulletArray firstObject];
bullet.center = CGPointMake(bullet.center.x + 4, bullet.center.y);
if (bullet.center.x > 500) {
bullet.hidden = YES;
[bulletArray removeObjectAtIndex:0];
}
}
}
-(void)setarray:(NSMutableArray *) array {
bulletArray = array;
}
-(NSMutableArray *)getArray {
return bulletArray;
}
To answer your two questions:
Question 1: Yes, there will be a performance increase for sure, regardless of how large the UIImageViews are.
Question 2: Simply set the imageView to nil: self.imageView = nil;
I guess I'm missing something obvious, but I simply can't figure out how to obtain the label of a CCMenuItemFont.
Background
I'm building a simple hangman game for the iPad. For entering the next guess, I've added 26 buttons to the UI (one for each letter of the alphabet) and wired them all to the same event handler.
Now, inside the event handler, I'd like to obtain the label of the button to update the current guess, but CCMenuItemFont apparently doesn't respond to text or label.
Problem
So - what method can I use to obtain the label of a CCMenuItem?
Code
Code for creating the buttons:
-(void)addButtons {
NSArray* charArray = [NSArray arrayWithObjects:
#"A",#"B",#"C",#"D",#"E",#"F",#"G",#"H",#"I",#"J",#"K",
#"L",#"M",#"N",#"O",#"P",#"Q",#"R",
#"S",#"T",#"U",#"V",#"W",#"X",#"Y",#"Z", nil];
[CCMenuItemFont setFontName:#"Marker Felt"];
[CCMenuItemFont setFontSize:45];
NSMutableArray* buttonArray = [NSMutableArray array];
for (unsigned int i=0; i < [charArray count]; ++i) {
CCMenuItemLabel* buttonMenuItem = [CCMenuItemFont
itemWithString:(NSString*)[charArray objectAtIndex:i]
target:self selector:#selector(buttonTapped:)];
buttonMenuItem.color = ccBLACK;
buttonMenuItem.position = ccp(60 + (i/13)*40, 600 - (i%13)*40);
[buttonArray addObject:buttonMenuItem];
}
CCMenu *buttonMenu = [CCMenu menuWithArray:buttonArray];
buttonMenu.position = CGPointZero;
[self addChild:buttonMenu];
}
And the event handler:
- (void)buttonTapped:(id)sender {
// Get a reference to the button that was tapped
CCMenuItemFont *button = (CCMenuItemFont *)sender;
[_guess addObject:[button text]]; // this throws an exception because text is the wrong method
[self paintCurrentGuess];
}
You're adding to your menu a CCMenuItemLabel, not a CCMenuItemFont (which actually extends the first one). In both cases, you need to access to the inner label containing the text.-
CCMenuItemLabel *button = (CCMenuItemLabel *)sender;
NSString *label = button.label.string;
THIS QUESTION HAS BEEN RE-WRITTEN IN ORDER TO PROPERLY COMMUNICATE THE INTENTIONS OF THE AUTHOR
I have a MasterDetail App that will eventually become a way to track an amount of "players" through a fixed amount of "challenges" (60 to be exact). The challenges are the same for every player, while the status of each player on the individual challenges may vary. All of the data about the players are being stored on a Postgres Database and then fetched using the JSONModel in the LeftViewController. I am getting all of the data correctly from the DB and I have successfully put it into a NSMutableDictionary. The data in the Dictionary looks like this (this is one player):
player = (
{
id = 9;
name = "Arthur Dent";
currentChallenge = "Cooking";
timeStarted = "04 Jul 2013 1:08 PM";
challengesDone = (
{
challengeName = "Flying";
timeCompleted = "04 Jul 2013 12:08 PM";
}
);
nextChallenge = "Swimming";
freePass = (
Running,
"Climbing"
);
},
As you can see, the player has some challenges "done" ("done"), some he has doesn't need to do ("pass") on, one he is doing ("currentChallenge") and the one challenge that he will do next ("nextChallenge"). For each of these states that a player can be in, I want the "Challenges" that are in the RightViewController to be color coded so you can understand what is happening at a glance. I need the "challenges" to light up different colors, based on the status of the player, which is determined by the data that is in my NSMutableDictionary.
By creating arrays from the data in the NSDictionary I can get the colors to change by doing this:
if ([freePassArray containsObject:#"challenge three name"])
{
UIImage *image = [UIImage imageNamed:#"status_pass.png"]; //status_pass.png is my color
[challengeThreeImageView setImage:image];
}
But if I did it like this, I would have to write 240 if and else if statements to cover all possible statuses on all 60 challenges.
My arrays only contain the data of the player selected because I pass the data over like so:
*LeftViewController.m * didSelectRowAtIndexPath
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
//Re-fetch the feed from the Postgres Database when a user selects an entry
[JSONHTTPClient getJSONFromURLWithString:#"http://myurl" completion:^(NSDictionary *json, JSONModelError *err) {
NSError* error = nil;
_feed = [[PostgresFeed alloc] initWithDictionary:json error:&error];
[[NSNotificationCenter defaultCenter] postNotificationName:#"myNotification" object:nil userInfo:[[json objectForKey:#"preclear"] objectAtIndex:indexPath.row]];
}];
Player *selectedPlayer = [_players objectAtIndex:indexPath.row];
if (_delegate)
{
[_delegate selectedPlayer:selectedPlayer];
}
}
So the images wont set for all of the players at once, but they also do not clear after another player is selected.
How do I get the colors to change without writing 240 if and else if statements? and how would I get the colors to clear when I select another player?
Assuming you have all the 50 images and you know which challenge corresponds to which image I would put the status image on top of the actual challenge image:
- (void)setChallengesDone:(NSArray*)doneAr next:(NSArray*)nextAr current:(NSArray*)currentAr andPass:(NSArray*)passAr
{
// remove additions from the previous selected player
UIView *prevAdditionsView = [self.view viewWithTag:122333];
while (prevAdditionsView != nil)
{
[prevAdditionsView removeFromSuperview];
prevAdditionsView = [self.view viewWithTag:122333];
}
for (int i=0; i<[doneAr count]; i++)
{
[self addChallengeImageAdditionWithImage:#"status_done" forChallenge:[doneAr objectAtIndex:i]];
}
for (int i=0; i<[nextAr count]; i++)
{
[self addChallengeImageAdditionWithImage:#"status_next" forChallenge:[nextAr objectAtIndex:i]];
}
for (int i=0; i<[currentAr count]; i++)
{
[self addChallengeImageAdditionWithImage:#"status_current" forChallenge:[currentAr objectAtIndex:i]];
}
for (int i=0; i<[passAr count]; i++)
{
[self addChallengeImageAdditionWithImage:#"status_free" forChallenge:[passAr objectAtIndex:i]];
}
}
- (void)addChallengeImageAdditionWithImage:(NSString*)image forChallenge:(NSString*)challengeTitle
{
UIImageView *challengeImage = [challenges objectForKey:challengeTitle];
CGRect frame = challengeImage.frame;
UIImageView *statusImg = [[[UIImageView alloc] initWithImage:[UIImage imageNamed:image]] autorelease];
[statusImg setFrame:frame];
[statusImg setAlpha:0.5]; // optionally add some transparency
[statusImg setTag:122333];
[self.view addSubview:statusImg];
[self.view bringSubviewToFront:challengeImage]; // bring challenge image to front
}
The simplest way to map your challenge titles with the UIImageViews is to maintain a NSDictionary with the challenge titles as keys, and a references to their corresponding UIImageViews as values.
This is an excellent example of the model/view concept.
Each challenge is a model with all the info required (challenge name, status, timestamp and others).
Each of your 50 views is a "challenge view" that knows how to render any challenge.
The status data is in the model; the status image is in the view.
Example:
-(void) drawChallenge: (Challenge*) challenge
{
// Draw challenge name as text
switch ([challenge status])
{
case done: // draw "done" image
case next: // draw "next" image
}
}
The idea is that the challenge itself should not know about how it is rendered, and the view should not know about what the challenge contains. The view is responsible for drawing it.
This could be packed into a ChallengeView subclass of UIView. Then, create your 50 or so ChallengeViews, and call [currentView drawChallenge: currentChallenge].
Essentially, when a new challenge is added, you create a view for it, and give the view a pointer to the challenge. When the challenge view is rendered by iOS, it'll draw "its" challenge as you have described in the drawChallenge method.
If you need a primer on the Model-View-Controller pattern, this section of the Apple docs is pretty good. You can disregard the Controller role in this situation.
I have an odd problem. I am using an AQGridView which has a method similar to table view controller which I have defined as follows:
- (AQGridViewCell *)gridView:(AQGridView *)aGridView cellForItemAtIndex:(NSUInteger)index
{
static NSString *CellIdentifier = #"IssueCell";
AQGridViewCell *cell = (AQGridViewCell *)[gridView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
[[NSBundle mainBundle] loadNibNamed:#"IssueCell" owner:self options:nil];
cell = [[AQGridViewCell alloc] initWithFrame:self.gridViewCellContent.frame
reuseIdentifier:CellIdentifier];
[cell.contentView addSubview:self.gridViewCellContent];
cell.selectionStyle = AQGridViewCellSelectionStyleNone;
}
IssueCell *content = (IssueCell *)[cell.contentView viewWithTag:1];
//This model object contains the title, picture, and date information
IssueModel *m = (IssueModel *)[self.issues objectAtIndex:index];
//If we have already downloaded the file, set the alpha to 1
if ([m hasPdfBeenDownloaded])
{
content.downloadIcon.hidden = YES;
content.imageView.alpha = 1;
content.progressView.hidden = YES;
}
else
{
if (m.pdfDownloadRequest && m.pdfDownloadRequest.isExecuting)
{
content.downloadIcon.hidden = YES;
content.imageView.alpha = .2;
content.progressView.hidden = NO;
}
else
{
content.downloadIcon.hidden = NO;
content.imageView.alpha = .2;
content.progressView.hidden = YES;
}
}
content.title.text = m.title;
// Only load cached images; defer new downloads until scrolling ends
if (!m.coverImageIcon)
{
if (self.gridView.dragging == NO && self.gridView.decelerating == NO)
{
[self startIconDownload:m forIndex:index];
}
content.imageView.image = [UIImage imageNamed:#"grid_cell_loading.png"];
}
else
{
content.imageView.image = m.coverImageIcon;
}
return cell;
}
My problem is since cells are reused, I lose the correct progress indication and updating of it. I am using ASIHTTP as follows:
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:model.issuePdfUrl];
request.userInfo = [NSDictionary dictionaryWithObjectsAndKeys:ic, #"cell", model, #"model", nil];
model.pdfDownloadRequest = request;
[request setShouldContinueWhenAppEntersBackground:YES];
[request setDelegate:self];
[request setDownloadDestinationPath:mediaPath];
[request setDownloadProgressDelegate:ic.progressView];
[request startAsynchronous];
The problem I am having is when I scroll down and then scroll backup, I lose the progressView I used to have as a reusable cell is used.
What is the correct way to do this so I don't lose the progress view?
As far as I understand, you bind progressView to a cell (first one, for example), then you scroll to a second cell, it is being created. Then third. This third possibly reuses first cell. But progressBar is not recreated, you reuse it.
So you have one progressBar but two ASIHTTPRequests that point to it. That's not very good.
What can I suggest? Well. You can update link of downloadProgressDelegate with the progressBar during gridView:cellForItemAtIndex: call. That is more ok path. You also can remove reusability of cells. That might help but is less ok and can cause problems (for example memory leaks) in the future.
Another way is to make some single method that gets all the progress messages. And uses these messages to map progress data to a grid model.