Another newbie question. I am trying to understand how to use scope effectively to organise my projects using classes to hold the data instead of having everything on the view controller. So, I am working on a couple of versions of a simple project to understand how scope works.
I have a view controller hooked to a view. In that view there are buttons that when clicked show images. I want to add another button that randomizes the images. I also have a class called "Cards" to hold the cards and the methods for creating and shuffling the cards. I have duplicated the project, so I have one that works and one that doesn't.
First project. These are the files:
view controller h file:
#import <UIKit/UIKit.h>
#import "Cards.h"
#interface ViewController : UIViewController
- (IBAction)buttonPressed:(id)sender;
#end
view contoller m file:
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Cards *instance = [[Cards alloc] init];
instance.images = [instance createImages];
NSLog(#"I've got %lu Images", (unsigned long)instance.images.count);
instance.shuffled = [instance shuffleImages];
NSLog(#"Image numbers shuffled: %#", instance.shuffled);
}
- (IBAction)buttonPressed:(id)sender {
//Nothing hooked to this yet
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
Cards h file:
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#interface Cards : NSObject
// Creating Images
#property NSMutableArray *images;
- (NSMutableArray*) createImages;
//Shuffling Images
#property NSMutableArray *shuffled;
- (NSMutableArray*) shuffleImages;
#end
Cards m file:
#import "Cards.h"
#implementation Cards
- (NSMutableArray*) createImages{
self.images = [[NSMutableArray alloc] initWithObjects:
[UIImage imageNamed:#"Image1.png"],
[UIImage imageNamed:#"Image2.png"],
[UIImage imageNamed:#"Image3.png"],
[UIImage imageNamed:#"Image4.png"], nil];
return self.images;
}
- (NSMutableArray*) shuffleImages{
NSUInteger imageCount = [self.images count];
NSMutableArray *localvar = [[NSMutableArray alloc]init];
for (int tileID = 0; tileID < imageCount; tileID++){
[localvar addObject:[NSNumber
numberWithInt:tileID]];
}
for (NSUInteger i = 0; i < imageCount; ++i) {
NSInteger nElements = imageCount - i;
NSInteger n = (arc4random() % nElements) + i;
[localvar exchangeObjectAtIndex:i
withObjectAtIndex:n];
}
return localvar;
}
#end
This works and I get the expected output on the console:
2015-12-31 23:43:44.885 VCScope[2138:533369] I've got 4 Images
2015-12-31 23:43:44.886 VCScope[2138:533369] Image numbers shuffled: (
0,
2,
3,
1
)
Second project:
What I want to do, is put a button to randomize the images only when the button is pressed and not as part of viewDidLoad. So, in my second project, I have the same files for the view controller.h and for both the Cards.h and Cards.m, but on the view controller.m I move the calling of the method for the shuffling of the cards to a UIButton method, like so:
new View controller m file:
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Cards *instance = [[Cards alloc] init];
instance.images = [instance createImages];
NSLog(#"I've got %lu Images", (unsigned long)instance.images.count);
}
- (IBAction)buttonPressed:(id)sender {
Cards *instance = [[Cards alloc] init];
instance.shuffled = [instance shuffleImages];
NSLog(#"Image numbers shuffled: %#", instance.shuffled);
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
This outputs to the console the following:
2015-12-31 23:32:07.495 4StackVCScope[2029:486608] I've got 4 Images
2015-12-31 23:32:11.924 4StackVCScope[2029:486608] Image numbers: (
)
So it's not working and I am guessing it's to do with scope. Can someone throw some light into this? thanks
Welcome to Stack Overflow. You mention you're a "newbie", but it would be helpful to know what background you have so I know how much detail is needed here.
Cards *instance = [[Cards alloc] init];
creates a fresh Cards instance in a local variable. You are doing this separately inside -viewDidLoad and in -buttonPressed:.
If you want one Cards object per ViewController, then the view controller needs to have per-instance storage for it. There are several possible ways to do this. Which one you pick is a question of code style and API design.
If the Cards instance is for internal use only, you can declare an ivar in your #implementation block:
#implementation ViewController {
Cards *_cards;
}
- (void)viewDidLoad { _cards = ... }
- (IBAction)buttonPressed:(id)sender { access _cards }
#end
(Ivars can be declared in the public #interface as well, but I wouldn't recommend that as it leaks implementation details.)
Or you can use a property in the public interface:
// in your .h file:
#interface ViewController
#property (nonatomic) Cards *cards;
#end
// in your #implementation:
- (void)viewDidLoad { self.cards = ... }
- (IBAction)buttonPressed:(id)sender { access self.cards }
A property can also be privately declared in a class extension:
// in your .m file:
#interface ViewController ()
#property (nonatomic) Cards *cards;
#end
Related
I have looked all over the place for anyone who has experienced this issue but have yet to find anything relevant, so I thought I'd ask it myself...
I have a custom object (HitterData) which I will use to populate cells in a UITableView, then two ViewControllers (one is hitterTableViewController, the other is a "detail" view controller labeled "AddPlayerViewController").
The problem is that I can add HitterData objects to the NSMutableArray in my Table VC, but only one, and then when I add another one using the detail view controller, the Mutable array is "reinitialized" and I can again only have one object at a time.
HitterObject:
#implementation HitterData.m
#synthesize hitterName = _hitterName;
#synthesize position = _position;
-(id)initWIthNameAndPosition:(NSString *)hitterName position:(NSString *)position {
if ((self = [super init])) {
self.hitterName = _hitterName;
self.position = _position;
}
return self;
}
HitterTableViewController.h
#import "HitterData.h"
#import "HitterDoc.h"
#import "AddPlayerViewController.h"
#interface HitterTableViewController : UITableViewController
#property (nonatomic, strong) NSMutableArray *hitters;
- (IBAction)backButton:(id)sender;
- (IBAction)addPlayerView:(id)sender;
-(void)addHitterObject:(HitterData *)hitter;
HitterTableViewController.m (only relevant to make this more readable)
#implementation HitterTableViewController
#synthesize hitters = _hitters;
- (void)viewDidLoad {
[super viewDidLoad];
self.hitters = [NSMutableArray array];
}
-(void)addHitterObject:(HitterData *)hitter {
if(_hitters != nil) {
[_hitters addObject:hitter];
} else {
_hitters = [NSMutableArray array];
[_hitters addObject:hitter];
NSLog(#"MutableArray is not valid");
}
}
AddPlayerViewController.h
#interface AddPlayerViewController : UIViewController <UITextFieldDelegate, UINavigationControllerDelegate>
#property (weak, nonatomic) IBOutlet UITextField *nameTextField;
#property (weak, nonatomic) IBOutlet UITextField *positionTextField;
#property (nonatomic) HitterTableViewController *hitterTable;
#property (nonatomic) NSString *hitterName;
#property (nonatomic) NSString *position;
//-(void)addNewHitterToHittersArray:(HitterData *)hitter;
- (IBAction)addPlayerToRoster:(id)sender;
AddPlayerViewController.m
#implementation AddPlayerViewController
#synthesize hitterTable;
- (void)viewDidLoad {
[super viewDidLoad];
hitterTable = [[HitterTableViewController alloc] init];
}
- (IBAction)addPlayerToRoster:(id)sender {
self.hitterName = [self.nameTextField text];
self.position = [self.positionTextField text];
HitterData *hitter = [[HitterData alloc] init];
hitter.hitterName = self.hitterName;
hitter.position = self.position;
[hitterTable addHitterObject:hitter];
ArraySingleton *arrayS = [[ArraySingleton alloc] init];
[arrayS initializeArray];
[arrayS addToHittersArray:hitter];
if (arrayS) {
NSLog(#"arrayS exists in AddPlayerVC");
} else {
NSLog(#"arrayS does not exist");
}
[self performSegueWithIdentifier:#"backToTeamTableViewController" sender:self];
}
Am I missing something here?
Guess based just on the code shown:
Every time you wish to add a player it looks like you create a new AddPlayerView/AddPlayerViewController. In turn that controller creates, in its viewDidLoad, a new HitterTableViewController - which of course has its own empty array. The code should instead be referencing the existing HitterTableViewController.
BTW: The common design pattern is MVC - model, view, controller - consider whether you are in your current situation because you've stored part of your model, the array, in your controller, and maybe both controllers should be referencing a common shared model object containing that array.
BTW: All those #synthesise statements are unneeded. In modern Obj-C synthesis is automatic and you rarely need these statements. Also it is generally recommended to not use the property backing variable directly, and certainly not when storing into the property as this breaks KVO. (There also appears to be a related typo in HitterData.m but as you don't report that as not working it is probably just a typo in your question and not code.)
HTH
AddPlayerViewController should know nothing about HitterTableViewController, return the new HitterData object with a delegate method.
- (IBAction)addPlayerToRoster:(id)sender
{
Hitter *hitter = [[Hitter alloc] init];
hitter.name = [self.nameTextField text];
hitter.position = [self.positionTextField text];
[self.delegate didAddHitter:hitter];
}
Then back in HitterTableViewController
- (void)didAddHitter:(Hitter *)hitter
{
[self.hitters addHitter:hitter];
[self dismissViewControllerAnimated:YES completion:nil];
}
I am developing a watchkit application and i have got a table view which must have functionality to delete rows. I saw a tutorial using content menu. But no idea how to delete row from the table. Please help me.
#import "HomeInterfaceController.h"
#import "HomeTableRowController.h"
#import "DetailHomeInterfaceController.h"
#interface HomeInterfaceController ()
#property (unsafe_unretained, nonatomic) IBOutlet WKInterfaceTable *homeTable;
#property (nonatomic,strong) NSArray *nameArr;
#end
#implementation HomeInterfaceController
- (void)awakeWithContext:(id)context {
[super awakeWithContext:context];
self.nameArr = [NSArray arrayWithObjects: #"Jill Valentine", #"Peter Griffin", #"Meg Griffin", #"Jack Lolwut",
#"Mike Roflcoptor", #"Cindy Woods", #"Jessica Windmill", #"Alexander The Great",
#"Sarah Peterson", #"Scott Scottland", #"Geoff Fanta", #"Amanda Pope", #"Michael Meyers",
#"Richard Biggus", #"Montey Python", #"Mike Wut", #"Fake Person", #"Chair",
nil];
[self setupTable];
// Configure interface objects here.
}
- (void)willActivate {
// This method is called when watch view controller is about to be visible to user
[super willActivate];
}
- (void)didDeactivate {
// This method is called when watch view controller is no longer visible
[super didDeactivate];
}
- (void)setupTable {
[self.homeTable setNumberOfRows:[self.nameArr count] withRowType:#"HomeTableRowController"];
for (NSInteger i = 0; i < self.nameArr.count; i++)
{
HomeTableRowController *row = [self.homeTable rowControllerAtIndex:i];
NSString *thisBook = [self.nameArr objectAtIndex:i];
[row.reminderHeadlineLabel setText:thisBook];
// [row.imageRow setImage:[UIImage imageNamed:#"abc.jpg"]];
}
}
- (void)table:(WKInterfaceTable *)table didSelectRowAtIndex:(NSInteger)rowIndex
{
NSDictionary *d=[NSDictionary dictionaryWithObject:#"hi" forKey:#"nm"];
[self presentControllerWithName:#"DetailHome" context:d];
}
- (IBAction)deleteButtonAction {
}
#end
You need to call removeRowsAtIndexes in your deleteButtonAction function:
// first create an index set with the index of the row you want to delete
NSIndexSet *indexes = [[NSIndexSet alloc] initWithIndex:index];
// then call the function
[self.homeTable removeRowsAtIndexes:indexes];
There is more info in the docs
I have a map view that instantiates a presentation model for running all it's logic in my application. I have a list component. User selects some data they want to see on the map. This gets sent to my model and once update is called on the view I pass an NSObject over to the Map view and then want to hand off this data to the PM in order to run logic on the view.
Here is my code for Map view. The very last line is where things crash "[pm showMKL:d];" gets called. If I comment out this line things run smooth.
This is an iOS 6 app so ARC is being used. Some searching suggested a memory leak but running the profiler didn't seem to suggest that. Also I was not able to produce the crash if I run the app with profiling on.
#import "MapView.h"
#implementation MapView
-(id)initWithModel:(MainDM*)dm:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
_dm = dm;
[_dm register:self];
pm = [[MapPM alloc] initWithModel:_dm];
CLLocationCoordinate2D coord = {.latitude = 32.61724, .longitude = -106.74128};
MKCoordinateSpan span = {.latitudeDelta = 1, .longitudeDelta = 1};
MKCoordinateRegion region = {coord, span};
[self setRegion:region];
}
return self;
}
-(void)update:(DataObject*)data
{
if([[data getType] isEqualToString:#"CURRENT_KML_CHANGE"])
{
KmlVO *d = (KmlVO*)[data getData];
[pm showMKL:d];
}
}
#end
Here is the .h for MapView
#import <MapKit/MapKit.h>
#import "MainDM.h"
#import "BBView.h"
#import "MapPM.h"
#interface MapView : MKMapView <BBView>
#property(nonatomic,strong)MainDM *dm;
#property(nonatomic,strong)MapPM *pm;
-(void)update:(DataObject*)data;
-(id)initWithModel:(MainDM*)dm:(CGRect)frame;
#end
Here is the MapPM code.
#import "MapPM.h"
#implementation MapPM
-(id)initWithModel:(MainDM *)dm
{
self = [super init];
if(self)
{
_baseMaps = [[NSMutableArray alloc] init];
_dm = dm;
}
}
-(void)showMKL:(KmlVO*)vo
{
if([_baseMaps containsObject:vo])
{
NSLog(#"not added");
}
else
{
NSLog(#"added kml");
[_baseMaps addObject:vo];
}
}
#end
here is the .h for the PM
#import <Foundation/Foundation.h>
#import "MainDM.h"
#import "KmlVO.h"
#interface MapPM : NSObject
#property(nonatomic, retain)NSMutableArray * baseMaps;
#property(nonatomic, retain)MainDM *dm;
-(id)initWithModel:(MainDM *)dm;
-(void)showMKL:(KmlVO*)vo;
#end
I didn't need to see your header files after all.
-(id)initWithModel:(MainDM *)dm
{
self = [super init];
if(self)
{
_baseMaps = [[NSMutableArray alloc] init];
_dm = dm;
}
}
You're not returning self, and as such, you're performing showMKL on an undefined object. Did you check your warnings?
Also, you're using properties (not instance variables within #interface like you led us to believe). As such, [pm showMKL:data] should really be [self.pm showMKL:data].
I've started cleaning up my app before publication - using "Instruments" Leak analyzer.
I found a leak I can't plug. So I built a simple project to illustrate the problem. Please see code below. I put a button on the view to test fire the procedure "test". It always generates a leak.
First the header and code for an object named "theObj"
#import <Foundation/Foundation.h>
#interface theObj : NSObject {
NSString* theWord;
}
#property (nonatomic,retain) NSString* theWord;
#end
#import "theObj.h"
#implementation theObj
#synthesize theWord;
-(id) initWithObjects: (NSString *) aWord;
{
if (self = [super init]){
self.theWord = aWord;
}
return self;
}
-(void) dealloc{
[theWord release];
[super dealloc];
}
#end
Now the view controller
#import <UIKit/UIKit.h>
#import "theObj.h"
#interface LeakAnObjectViewController : UIViewController {
NSMutableArray* arrObjects;
}
- (IBAction)test;
#end
#import "LeakAnObjectViewController.h"
#implementation LeakAnObjectViewController
- (IBAction)test {
if (arrObjects == nil)
arrObjects = [[NSMutableArray alloc] init];
NSString* aStr = #"first";
[arrObjects addObject:[[theObj alloc] initWithObjects:aStr]];
[arrObjects removeAllObjects];
}
You alloc the object, which means you own it. Then you give it to the array, which means the array owns it as well. Then the array removes it, so you are the only owner. But you don't have a reference to the object anymore, so you can't release it, so it's just leaked.
Someone really needs to learn the rules around memory management. Specifically as it pertains to ownership, etc.
I've been stuck on this for days and each time I come back to it I keep making my code more and more confusing to myself, lol. Here's what I'm trying to do. I have table list of charges, I tap on one and brings up a model view with charge details. Now when the model is presented a object is created to fetch a XML list of users and parses it and returns a NSMutableArray via a custom delegate. I then have a button that presents a picker popover, when the popover view is called the user array is used in an initWithArray call to the popover view. I know the data in the array is right, but when [pickerUsers count] is called I get an EXC_BAD_ACCESS. I assume it's a memory/ownership issue but nothing seems to help. Any help would be appreciated.
Relevant code snippets:
Charge Popover (Charge details model view):
#interface ChargePopoverViewController .....
NSMutableArray *pickerUserList;
#property (nonatomic, retain) NSMutableArray *pickerUserList;
#implementation ChargePopoverViewController
#synthesize whoOwesPickerButton, pickerUserList;
- (void)viewDidLoad {
JEHWebAPIPickerUsers *fetcher = [[JEHWebAPIPickerUsers alloc] init];
fetcher.delegate = self;
[fetcher fetchUsers];
}
-(void) JEHWebAPIFetchedUsers:(NSMutableArray *)theData {
[pickerUserList release];
pickerUserList = theData;
}
- (void) pickWhoPaid: (id) sender {
UserPickerViewController* content = [[UserPickerViewController alloc] initWithArray:pickerUserList];
UIPopoverController *popover = [[UIPopoverController alloc] initWithContentViewController:content];
[popover presentPopoverFromRect:whoPaidPickerButton.frame inView:self.view permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
content.delegate = self;
}
User Picker View Controller
#interface UserPickerViewController .....
NSMutableArray *pickerUsers;
#property(nonatomic, retain) NSMutableArray *pickerUsers;
#implementation UserPickerViewController
#synthesize pickerUsers;
-(UserPickerViewController*) initWithArray:(NSMutableArray *)theUsers {
self = [super init];
if ( self ) {
self.pickerUsers = theUsers;
}
return self;
}
- (NSInteger)pickerView:(UIPickerView *)thePickerView numberOfRowsInComponent:(NSInteger)component {
// Dies Here EXC_BAD_ACCESS, but NSLog(#"The content of array is%#",pickerUsers); shows correct array data
return [pickerUsers count];
}
I can provide additional code if it might help. Thanks in advance.
You declare the ivar holding the array as this...
#property (nonatomic, retain) NSMutableArray *pickerUserList;
But then you have a method implemented like this:
-(void) JEHWebAPIFetchedUsers:(NSMutableArray *)theData {
[pickerUserList release];
pickerUserList = theData;
}
You aren't retaining theData and you aren't calling the synthesized setter. If you did Build and Analyze, it should catch this problem and tell you about it. If not, file a bug.