Who owns my UITextField

Discussion in 'iOS Programming' started by larswik, Sep 24, 2011.

  1. larswik macrumors 68000

    Joined:
    Sep 8, 2006
    #1
    I am getting an EXC_BAD_ACESS when I click on my UITextField that I created programmaticly.In my ViewController I have a button that creates a new UIImageView and adds it as a subView. Since I had off the UITextImages and release the ListItem Object It should become the responsibility of the UIImageView to manage it's memory at this point, right? It crashes as soon as I click in the textField which I assume is caused but an object that is already relased.
    Code:
    -(IBAction)createSheet:(id) sender{
        MoneySheet *newSheet = [[MoneySheet alloc] initWithNibName:@"MoneySheet" bundle:nil];
        [self.view addSubview: newSheet.view];                           
        [newSheet release];
    }
    
    Here is the ViewDidLoad in my MoneySheet.m (MoneySheetViewController)
    Code:
    - (void)viewDidLoad
    {
        UIImage * moneySheet = [UIImage imageNamed:@"moneyListSheet.png"]; [COLOR="SeaGreen"]//Create BackGround Image[/COLOR]
        UIImageView * moneySheetView =[[UIImageView alloc]initWithImage:moneySheet];
        moneySheetView.frame = CGRectMake(0, 0, 320, 480);
        [self.view addSubview:moneySheetView];
        
        UILabel *headerInfoLabel = [[[UILabel alloc ]initWithFrame:CGRectMake(25, 0, 200, 30)]autorelease];[COLOR="SeaGreen"]// add Label[/COLOR]
        headerInfoLabel.text = @"Money List";
        headerInfoLabel.backgroundColor = [UIColor clearColor];
        headerInfoLabel.font = [UIFont fontWithName:@"Marker Felt" size: 22.0];
        [self.view addSubview:headerInfoLabel];
        
        UILabel *headerItemList = [[[UILabel alloc ]initWithFrame:CGRectMake(63, 25, 300, 30)]autorelease]; [COLOR="SeaGreen"]//Add Label[/COLOR]
        headerItemList.text = @"Items          Monthly     Total";
        headerItemList.backgroundColor = [UIColor clearColor];
        headerItemList.font = [UIFont fontWithName:@"Marker Felt" size: 14.0];
        headerItemList.font = [UIFont boldSystemFontOfSize:17.0];    
        [self.view addSubview:headerItemList];
        
        float rowDistance = 55.0;
       [COLOR="Red"] for (int i = 0; i < 1; i++) { // for loop to add UITextFields
         
            [COLOR="Blue"]ListItem *row = [[ListItem alloc] init];[/COLOR]
            [self.view addSubview:[row makeItem:rowDistance]];
            [self.view addSubview:[row makeMonthly:rowDistance]];
            [self.view addSubview:[row makeTotal:rowDistance]];
            [self.view addSubview:[row makeDollarSigns:rowDistance]];
            [self.view addSubview:[row makeDollarSignsTwo:rowDistance]];  
            rowDistance = rowDistance + 30;;
            [COLOR="Blue"][row release];[/COLOR][/COLOR]
        }
        [super viewDidLoad];
        // Do any additional setup after loading the view from its nib.
    }
    
    And the code for creating a UITextField that is located in a Class called ListItem.m with a NSObject as it's super class
    Code:
    -(UITextField *)makeItem:(float) rowHeight{
        
        firstOne = [[[UITextField alloc] initWithFrame:CGRectMake(15.0f, rowHeight, 130.f, 25.0f)] autorelease];
        firstOne.borderStyle = UITextBorderStyleRoundedRect;
        [firstOne setReturnKeyType:UIReturnKeyDefault];
        [firstOne setEnablesReturnKeyAutomatically:YES];
        [firstOne setDelegate:self];
        return firstOne;
    }
    
    Why is it being released? I have spent the night trying to figure this out.
     
  2. jiminaus macrumors 65816

    jiminaus

    Joined:
    Dec 16, 2010
    Location:
    Sydney
    #2
    I think the trouble might be coming from the line hightlighted in red above.

    Self is the ListItem object created and then released in MoneySheetViewController's viewDidLoad. UITextField's delegate property is declared as assign, not retain. So after viewDidLoad, the UITextField objects now have delegates pointing to a released object. When you click/touch on the UITextField, it tries to send textFieldShouldBeginEditing: to the now released delegate, causing an EXC_BAD_ACCESS.
     
  3. larswik thread starter macrumors 68000

    Joined:
    Sep 8, 2006
    #3
    That got rid of the crash. In the last 30 mins I looked over the code and I noticed that I was not assigning the the new UITextFields to the UIImageView. I was assigning them to self.
    Code:
    ListItem *row = [[ListItem alloc] init];
    [self.view addSubview:[row makeItem:rowDistance]];
    [row release];
    I then changed the code to this which I think I now added the UITextFIeld to moneySheetView which is my UIImageView.
    Code:
     ListItem *row = [[ListItem alloc] init];
    [moneySheetView addSubview:[row makeItem:rowDistance]];
    [row release];
    I could no longer click on the UITextField and it did nothing and then discovered this
    Code:
    moneySheetView.userInteractionEnabled = YES;
    Now I can edit the text and it does not crash and the textField is editable. I am assuming that since I have released it that my UIImageView is now the owner of my new UITextField objects? (as I get a memory management lesson). I am now trying to figure out how to 'resignFirstResponder' to dismiss the keyboard which it is not doing. I know it has to do with the delegate of the UITextField but that is still a gray area for me.

    Thanks Jim!
     
  4. dejo Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
    #4
    Just be careful about thinking of ownership as a singular thing. Multiple objects can "own" an object. Have you read through the entire Memory Management Programming Guide? If not, you should.
     
  5. larswik thread starter macrumors 68000

    Joined:
    Sep 8, 2006
    #5
    Yes I have read that a couple of times so far. In theory and in practice I understand what is happening. But I seem to only get it after I practice a lot with it, then it becomes second nature.

    -Lars
     
  6. North Bronson, Sep 24, 2011
    Last edited: Sep 24, 2011

    North Bronson macrumors 6502

    Joined:
    Oct 31, 2007
    Location:
    San José
    #6
    You might want to try to make your views to be self-contained factories that can create new views without too much outside help or influence. For example, consider Apple's UIPickerView. This is a pretty complex view; there are subviews and gradients and different things everywhere. The object that asks for a UIPickerView is not responsible for setting up the primitive ingredients of the picker view hierarchy. The picker view knows how to set itself up when it is created.

    Imagine if it was the other way around. Imagine if the object that asks for a UIPickerView was also responsible for creating and managing fundamental pieces of the picker view. It would significantly harm the reusability of the object. It is true that the picker view needs a data source to describe the data that is displayed; the developers have factored out that code that is common to create every picker view (and wrote it in the class) and that code that is customized for every picker view (and let you create it in the datasource protocol).

    If you are adding subviews to your main view in the controller, factor out that code and make it part of your own custom view. The custom view becomes the main view of the controller. When you create the custom view, make it smart enough to create the view hierarchy on its own. This will be a cleaner approach and will reinforce the MVC separation.

    When you create any custom classes in your application, suffix the class with either:

    Model
    Controller
    View

    What is ListItem?

    ListItemModel?
    ListItemController?
    ListItemView?

    Ask yourself what this class is supposed to do. Ask yourself if that class belongs with the models, the views, or controllers. If it is not clear what category that class should be, do not include the class. Figure out another way to do the work. You should be clear about what purpose every class serves. The work that the class is doing should be appropriate for its category.

    If you create a model class that is responsible for creating and managing UIView subclasses, something is wrong.

    If you create a controller class that is responsible for creating and managing UIView subclasses, that can be appropriate in certain circumstances.

    The cleanest approach is to create a view class that is responsible for creating and managing your arbitrary views.
     
  7. larswik thread starter macrumors 68000

    Joined:
    Sep 8, 2006
    #7
    Hummm, so my approach is wrong maybe. I basically have 2 viewControllers. First page or ViewController the user clicks to create a new money list page.

    This is a separate ViewController with a new xib that comes up. On the second page the user would end up hitting a button (now it is just a for loop) that would add 3 UITextFields. These UITextFields are in a Separate Class (Which is the ListItems) that get created and added the UITextField Objects to the UIImageView.

    I am trying to learn how to work with Objects better. It seemed to make sense to have these UITextFields in a separate Class. I create an Object from that class every time a button is pressed, the 3 UITextFields get added to the UIImageView and then the Object is released.

    Is that not a good way of working with Objects? I thought this would be the best way of adding new UITextFields to the UIImageView. I could reuse this code over and over again by creating the object as needed.
     

    Attached Files:

  8. larswik thread starter macrumors 68000

    Joined:
    Sep 8, 2006
    #8
    I kind if got it running. I started a new project to test it and this time I did not create the 'ListItem' Class to create the 3 UITextFields. Instead I created 3 Methods, 1 for each UITextField in the ViewController.m and added the UITextFields for now in the ViewDidLoad Method using SELF. This now dismisses the keyboard. The end result will be to have a button create a row and not a for loop, I am just testing it.

    I still don't get why I can't have the creation of all 3 UITextFields in a separate class, then make an object from that class and add them to the view but it works.

    Code:
    - (void)viewDidLoad
    {
        UIImage * moneySheet = [UIImage imageNamed:@"moneyListSheet.png"]; //Create BackGround Image
        UIImageView * moneySheetView =[[UIImageView alloc]initWithImage:moneySheet];
        moneySheetView.userInteractionEnabled = YES;
        moneySheetView.frame = CGRectMake(0, 0, 320, 480);
        
        self.view.userInteractionEnabled = YES;
        [self.view addSubview:moneySheetView];
    
        UILabel *headerInfoLabel = [[[UILabel alloc ]initWithFrame:CGRectMake(25, 0, 200, 30)]autorelease];// add Label
        headerInfoLabel.text = @"Money List";
        headerInfoLabel.backgroundColor = [UIColor clearColor];
        headerInfoLabel.font = [UIFont fontWithName:@"Marker Felt" size: 22.0];
        [self.view addSubview:headerInfoLabel];
        
        
        float rowHeight = 55.0;
        
        for (int i = 0 ; i < 3; i++) {
          
        [self.view addSubview:[COLOR="Red"][self newFieldItem: rowHeight][/COLOR]];
        [self.view addSubview:[COLOR="Red"][self newFieldMonthly: rowHeight][/COLOR]];
        [self.view addSubview:[COLOR="Red"][self newFieldTotal: rowHeight][/COLOR]];
            rowHeight += 30;
        }
        
        [super viewDidLoad];
    }
    
    - (BOOL)textFieldShouldReturn:(UITextField *)textField {
        [textField resignFirstResponder];   
        return YES;
    }
    
    
    -(UITextField *)[COLOR="Red"]newFieldItem[/COLOR]:(float) atPosition{
        UITextField *firstOne = [[[UITextField alloc] initWithFrame:CGRectMake(15.0f, atPosition, 130.f, 25.0f)] autorelease];
        firstOne.borderStyle = UITextBorderStyleRoundedRect;
        firstOne.delegate = self;
        firstOne.returnKeyType = UIReturnKeyDefault;
        return firstOne;
    }
    
    -(UITextField *)[COLOR="Red"]newFieldMonthly[/COLOR]:(float) atPosition{
        UITextField *SecondOne = [[[UITextField alloc] initWithFrame:CGRectMake(165.0f, atPosition, 50.f, 25.0f)] autorelease];
        SecondOne.borderStyle = UITextBorderStyleRoundedRect;
        SecondOne.delegate = self;
        SecondOne.returnKeyType = UIReturnKeyDefault;
        return SecondOne;
    }
    -(UITextField *)[COLOR="Red"]newFieldTotal[/COLOR]:(float) atPosition{
        UITextField *ThirdOne = [[[UITextField alloc] initWithFrame:CGRectMake(235.0f, atPosition, 70.f, 25.0f)] autorelease];
        ThirdOne.borderStyle = UITextBorderStyleRoundedRect;
        ThirdOne.delegate = self;
        ThirdOne.returnKeyType = UIReturnKeyDefault;
        return ThirdOne;
    }
    
     
  9. admanimal macrumors 68040

    Joined:
    Apr 22, 2005
    #9
    Have you looked at any of Apple's sample projects (the ones in the developer center, not the Xcode templates)? There are many that demonstrate how to set up apps with multiple screens and all kinds of interface elements and whatnot. Some of them can be a little messy code-wise, but in general they are good examples.
     
  10. larswik thread starter macrumors 68000

    Joined:
    Sep 8, 2006
    #10
    I will look into that as well. I had been watching the Stanford University videos and how they build apps.

    It's very frustrating. I know Objects are great for code that you want to reuse. I made a Dice app. I press a button, the button instantiates my Dice Object which rolls a random value, that value is returned and the Dice object is released. I get that, I have done that.

    Same thing with my UITextFields as my Dice button, same principle. Hit a button, create textFields, but have to hand them off before I release the object that created them so something else has ownership of them. That works. But dismissing the keyboard is not working. I added this code to every ViewController hoping to see the NSLog printed when the return key was pressed and nothing.
    Code:
    - (BOOL)textFieldShouldReturn:(UITextField *)textField {
        [textField resignFirstResponder];
        NSLog(@"Working!!!");
        return YES;
    }
    
    In the end I got it to work a different way. I wrote all the code listed above in the same ViewController, and now call [self newFieldItem: rowHeight] to add the textField. The [self newFieldItem: rowHeight] should be RowOfUITextField *createRow = [[.... Seems smarter to call an object to do this but o well, it works, Procedural Objective C for me.

    Thanks for the help!
     
  11. North Bronson macrumors 6502

    Joined:
    Oct 31, 2007
    Location:
    San José
    #11
    This is getting better, but it still not quite optimal. Here is an example of where this could come back to hurt you: you might build this iPhone application for your client. The client puts the application on the store and then makes a lot of money and comes back and tells you to make this a universal application. You decide that the iPad version will have a different view layout. The same view from the iPhone will be in one corner of the iPad screen, and you will display more detailed information around the sides of that view.

    A good approach would be to create one view controller for your iPhone and one view controller for your iPad. The problem with creating subviews in your controller is that now each controller will be responsible for going through this work. It is not just controllers. Any object that wants to use this view has to be smart enough to create the proper text fields.

    The cleaner approach is to build one view. When that view is created, it does the work of creating any necessary subviews. When any object wants to use that view, it can just be one line of code to create and you are done. There should still be some customization that can be done, but the basics of creating the view (things that do not change depending on what object is creating the view) should be handled by that actual view.
     
  12. larswik thread starter macrumors 68000

    Joined:
    Sep 8, 2006
    #12
    Thanks. I went back to square one today after I got frustrated. I found a good tutorial on UINavigationControllers. After that everything fell into place and started working. I think I got off to the wrong start and it kept going wrong. On my MainPageViewController I have 4 buttons and use this code to push the new ViewController on.
    Code:
    - (IBAction)personalExpenseButton:(id)sender {
        PersonalExpenses *myExpenses = [[PersonalExpenses alloc]init];
        myExpenses.title = @"Personal Expenses";
        [self.navigationController pushViewController:myExpenses animated:YES];
        [myExpenses release];
    }
    
    I have been using a combination of IB and programaticlly making the UITextFields and the keyboard has no problem dismissing. It's funny how when you are coding well, everything is clear and falls into place.

    The goal tomorrow is to figure out how to take the UITextField value and save it to an NSString, then save that to an NSMutableArray or NSMutableDictionary. When I press the back button on the NavigationController it releases the ViewController object. Then when they press the button again to bring up the myExpense and the info will be read back into the UiTextFields from the array or Dict.

    Thanks again guys for all your help. I do need to study from my Java midterm at some point this week :)
     
  13. admanimal macrumors 68040

    Joined:
    Apr 22, 2005
    #13
    Here's a little nitpick for you: your classes should be named so that it is clear when they are subclasses of other important classes (other than plain NSObject or NSManagedObject). So if your PersonalExpenses class is a subclass of UIViewController, it should be called PersonalExpensesViewController or PersonalExpensesController. When I see a class that just has a generic name like PersonalExpenses, I expect it to be a completely custom class (i.e. a subclass of NSObject).
     

Share This Page