I have a long UIViewController with lots of sections and rows. In spite of having separate methods for each of these sections, it still becomes cumbersome jumping to different methods. Is there a best way to design such class? I was thinking of having a category for each of these sections? is it a good idea?
I've found that code related to delegate methods become a large part of my view controllers. One strategy, described in this article at objc.io, is to move data source delegate methods to their own class.
In the last year I started to move out as much code as possible from my view controllers, attempting to create «Lighter View Controllers». There-for you can use objects that implement one — and only one — certain aspect of the functionality that the view controller will have. You could call it Sub Controller, but I and others use the name «Intentions» to express the fact, that each of this objects has one intention.
I experimented with different kinds, from target/action to block based, that I finally found to be the most useful.
An example from real code:
I habe a cash register app that need to communicate via wifi with thermal printers.
To add a printer I have the AddPrinterViewController with textfields for ip address, port and name/location.
Instead of implementing the view controller as delegate for all the textfields I create on class that will serve as delegate for one textfield and has a block-based validation.
#interface TextfieldDelegateIntention : NSObject
#property(nonatomic, weak, readonly) UITextField *textField;
#property (nonatomic, copy) BOOL (^validationBlock)(UITextField *textField);
-(instancetype)initWithTextField:(UITextField *)textField
validationBlock: (BOOL (^)(UITextField *textField)) validationBlock;
-(BOOL)validate;
#end
#import "TextfieldDelegateIntention.h"
#interface TextfieldDelegateIntention ()<UITextFieldDelegate>
#property(nonatomic, weak) UITextField *textField;
#end
#implementation TextfieldDelegateIntention
-(instancetype)initWithTextField:(UITextField *)textField
validationBlock: (BOOL (^)(UITextField *textField)) validationBlock
{
self = [super init];
if(self){
self.validationBlock = validationBlock;
self.textField = textField;
self.textField.delegate = self;
}
return self;
}
-(BOOL)validate
{
if (self.validationBlock) {
return self.validationBlock(self.textField);
}
return NO;
}
-(BOOL)textFieldShouldReturn:(UITextField *)textField
{
[textField resignFirstResponder];
return YES;
}
-(void)textFieldDidEndEditing:(UITextField *)textField
{
[textField resignFirstResponder];
}
#end
I setup the view controller with the three textfields in the storyboard.
The IP (version 4 only) address' textfield could look like this:
TextfieldDelegateIntention *ipAddresIntention = [[TextfieldDelegateIntention alloc] initWithTextField:self.ipTextField validationBlock:^BOOL(UITextField *textField) {
NSArray *components = [textField.text componentsSeparatedByString:#"."];
if ([components count] == 4) {
__block BOOL compsAreValidNumbers = YES;
[components enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
if ([obj integerValue] > -1 && [obj integerValue] < 256 ) {
} else {
compsAreValidNumbers = NO;
*stop = YES;
}
}];
return compsAreValidNumbers;
}
return NO;
}];
A overly simple validation, but useful to assist the user with the correct input.
The complete view controller's code looks like
#import <UIKit/UIKit.h>
#import "BaseModalViewController.h"
#class NamendPOSNetworkPrinter;
#class PrinterProvider;
#interface AddPrinterViewController : BaseModalViewController
#property (nonatomic, strong) PrinterProvider *printerProvider;
#property (nonatomic, copy) void(^printerPreferecesEntered)(NSDictionary *printerDict);
#end
#import "AddPrinterViewController.h"
#import "TextfieldDelegateIntention.h"
#import "ButtonIntention.h"
#import "PrinterProvider.h"
#interface AddPrinterViewController ()
#property (weak, nonatomic) IBOutlet UITextField *nameTextField;
#property (weak, nonatomic) IBOutlet UITextField *ipTextField;
#property (weak, nonatomic) IBOutlet UITextField *portTextField;
#property (nonatomic, strong) NSArray *textFieldIntentions;
#property (nonatomic, strong) ButtonIntention *okIntention;
#property (weak, nonatomic) IBOutlet UIButton *okButton;
#end
#implementation AddPrinterViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
TextfieldDelegateIntention *nameIntention = [[TextfieldDelegateIntention alloc] initWithTextField:self.nameTextField validationBlock:^BOOL(UITextField *textField) {
if ([textField.text length] > 0) {
return YES;
}
return NO;
}];
TextfieldDelegateIntention *ipAddresIntention = [[TextfieldDelegateIntention alloc] initWithTextField:self.ipTextField validationBlock:^BOOL(UITextField *textField) {
NSArray *components = [textField.text componentsSeparatedByString:#"."];
if ([components count] == 4) {
__block BOOL compsAreValidNumbers = YES;
[components enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
if ([obj integerValue] > -1 && [obj integerValue] < 256 ) {
} else {
compsAreValidNumbers = NO;
*stop = YES;
}
}];
return compsAreValidNumbers;
}
return NO;
}];
TextfieldDelegateIntention *portIntention = [[TextfieldDelegateIntention alloc] initWithTextField:self.portTextField validationBlock:^BOOL(UITextField *textField) {
if ([textField.text integerValue] > 1023 && [textField.text integerValue]< 65536) {
return YES;
}
return NO;
}];
self.textFieldIntentions = #[nameIntention, ipAddresIntention, portIntention];
__block typeof(self) weakSelf = self;
ButtonIntention *okIntention = [[ButtonIntention alloc] initWithButton:self.okButton actionBlock:^(UIButton *button) {
typeof(weakSelf) strongSelf = weakSelf;
if (strongSelf) {
__block BOOL formIsValid = YES;
[strongSelf.textFieldIntentions enumerateObjectsUsingBlock:^(TextfieldDelegateIntention *intention, NSUInteger idx, BOOL *stop) {
BOOL isValid = [intention validate];
if (!isValid) {
intention.textField.backgroundColor = [UIColor redColor];
formIsValid = NO;
} else {
intention.textField.backgroundColor = [UIColor greenColor];
}
}];
if (formIsValid) {
if (self.printerPreferecesEntered) {
self.printerPreferecesEntered(#{
#"name": strongSelf.nameTextField.text,
#"ipAddress": strongSelf.ipTextField.text,
#"port": #([strongSelf.portTextField.text integerValue])
});
}
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[strongSelf dismissViewControllerAnimated:YES completion:NULL];
});
}
}
}];
self.okIntention = okIntention;
}
#end
As you see I can use simple blocks to add different behavior to the three textfields and only one method is overwritten — no other added.
Another example with email validation from my blog: Lighter ViewControllers with Block-based Intentions
A tableview's datasource is also very easy implemented as Intention.
#import <UIKit/UIKit.h>
#class PrinterProvider;
#interface PrinterDataSource : NSObject <UITableViewDataSource>
#property (nonatomic, strong) PrinterProvider *printerProvider;
#end
#import "PrinterDataSource.h"
#import "VSPOSNetworkPrinter.h"
#import "PrinterProvider.h"
#interface PrinterDataSource ()
#end
#implementation PrinterDataSource
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [[self.printerProvider allPrinters] count];
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"PrinterCell" forIndexPath:indexPath];
cell.textLabel.text = [[self.printerProvider allPrinters][indexPath.row] ipAddress];
cell.detailTextLabel.text = [[self.printerProvider allPrinters][indexPath.row] name];
return cell;
}
#end
Use it as
#import "PrinterViewController.h"
#import "AddPrinterViewController.h"
#import "ButtonIntention.h"
#import "BarButtomItemIntention.h"
#import "NamendPOSNetworkPrinter.h"
#import "PrinterProvider.h"
#import "PrinterDataSource.h"
#interface PrinterViewController ()
#property (weak, nonatomic) IBOutlet UITableView *tableView;
#property (weak, nonatomic) IBOutlet UIBarButtonItem *addButton;
#property (nonatomic, strong) BarButtomItemIntention *addIntention;
#property (nonatomic, strong) AddPrinterViewController *addPrinterViewController;
#property (strong, nonatomic) IBOutlet PrinterDataSource *printerDataSource;
#property (nonatomic, strong) PrinterProvider *printerProvider;
#end
#implementation PrinterViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.printerDataSource.printerProvider = self.printerProvider;
self.addPrinterViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"AddPrinterViewController"];
typeof(self) weakSelf = self;
[self.addPrinterViewController setPrinterPreferecesEntered:^(NSDictionary *printerDict) {
NamendPOSNetworkPrinter *printer = [[NamendPOSNetworkPrinter alloc] initWithName:printerDict[#"name"]
ipAddress:printerDict[#"ipAddress"]
port:printerDict[#"port"]];
typeof(self) strongSelf = weakSelf;
if (strongSelf) {
[strongSelf.printerProvider addPrinter:printer];
[strongSelf.tableView reloadData];
}
}];
self.addPrinterViewController.printerProvider = self.printerProvider;
self.addIntention = [[BarButtomItemIntention alloc] initWithButtonItem:self.addButton
actionBlock:^(UIBarButtonItem *buttonItem)
{
typeof(weakSelf) strongSelf = weakSelf;
if(strongSelf){
[strongSelf presentViewController:strongSelf.addPrinterViewController
animated:YES completion:^{
}];
}
}];
}
#end
The charm about this approach is that you can independently create and subclass view controller and intentions, and that I can rearrange them as needed easily. Actually apple started to advertise a similar approach in the recent wwdc video «Advanced User Interfaces with Collection Views»
I would suggest using #pragma mark [divider name] to split up your class into sections so you can easily group methods. Otherwise creating subclasses where it makes sense to handle contained functionality is you're only option. I've seen 8,000+ line view controller classes, so sometimes it's unavoidable if you're too deep to break it all apart.
This is more like a programming philosophy.
Objective-C has lengthy methods (name and declaration), so the best way to ease the code is to delegate any repetitive pattern to an external class.
For example, I create a class reserved solely of returning numerical operations, such as arithmetic operations on NSNumbers, strings, etc. Another one that just returns booleans on an input.
For example, if you verify the validity of a number and increment it, or set it to 1, in even just two places in your code, rather than having somethings like this:
if (variableName != nil && variableName.intValue > 0)
{
variableName2 = [NSNumber numberWithInt:(variableName.intValue + 1)];
}
else
{
variableName2 = [NSNumber numberWithInt:1];
}
It would be easier to have this function outside (in an "Arithmetics" class for example), and call it like follow:
variableName2 = [Arithmetics incrementNumber:variableName];
If you replace all your code like this, by having well named classes and easy to understand methods (names), your code will be more logically readable and easy to follow, which is the philosophy of Objective-C (you get also the bonus of being able to reuse these methods in all your code, keeping the same logic everywhere).
Related
I'm trying to hide a UILabel in every object (UIView) of the same class in my app. I tried something with a static class method but I'm not able to access to the instance variable.
MyView.h
#interface MyView: UIView
{
UILabel *titleLabel;
UILabel *subTitleLabel;
}
+(void)hideLabel;
#end
MyView.m
#import "MyView.h"
#implementation TempNodeView
+(void)hideLabel
{
[titleLabel setHidden:YES];
}
#end
What is the best (proper) solution in this kind of situation?
Thank you very much
For your case I suggest you to have references to all of this objects. This means you will need to add the object into some static array in its constructor.
The problem then occurs that the views will be retained by the array so you need another object that will be a container for a weak reference to your object so you avoid memory leak.
Try to build something like the following:
static NSMutableArray *__containersPool = nil;
#interface MyViewContainer : NSObject
#property (nonatomic, weak) MyView *view;
#end
#implementation MyViewContainer
#end
#interface MyView : UIView
#property (nonatomic, readonly) UILabel *labelToHide;
#end
#implementation MyView
+ (NSMutableArray *)containersPool {
if(__containersPool == nil) {
__containersPool = [[NSMutableArray alloc] init];
}
return __containersPool;
}
// TODO: override other constructors as well
- (instancetype)initWithFrame:(CGRect)frame {
if((self = [super initWithFrame:frame])) {
MyViewContainer *container = [[MyViewContainer alloc] init];
container.view = self;
[[MyView containersPool] addObject:container];
}
return self;
}
+ (void)setAllLabelsHidden:(BOOL)hidden {
for(MyViewContainer *container in [[self containersPool] copy]) {
if(container.view == nil) {
[[self containersPool] removeObject:container]; // It has been released so remove the container as well
}
else {
container.view.labelToHide.hidden = hidden;
}
}
}
#end
I'm populating an object's fields from Labels, when displaying in Log the label is the correct value, but the object's field is null. I'm coming over from an Android/ Java background and this is just awkward.
Any help would be great.
To be clear, the "soil type field" log shows "example"while the "soil type" log shows (null)
- (IBAction)saveButton:(id)sender {
Soil *thisSoil = self.thisSoil;
thisSoil.soilType = self.soilNameField.text;
NSLog(#"soil type field %#", self.soilNameField.text);
NSLog(#"soil type: %#", thisSoil.soilType);
thisSoil.frictionAngle = [self.frictionAngleValue.text integerValue];
if ([self.soilUnitsSwitch isOn] ) {
thisSoil.cohesion = [self.cohesionValue.text doubleValue];
thisSoil.unitWeight = [self.unitWeightValue.text doubleValue];
}else{
thisSoil.cohesion = [self.cohesionValue.text doubleValue];
thisSoil.unitWeight = [self.unitWeightValue.text doubleValue];
}
[self.delegate SoilCreatorViewController:self didFinishItem:thisSoil];
[self.navigationController popViewControllerAnimated:YES];
}
The entire controller .m file
#import "SoilCreatorViewController.h"
#define IMPERIAL_TO_METRIC 0.3048
#define KG_TO_LBS 2.2
#interface SoilCreatorViewController ()
#end
#implementation SoilCreatorViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)soilQuestions:(id)sender {
[self popupmaker :#"Friction Angle" : #"Phi is the angle of internal friction for soil, which governs soil strength and resistance. This value should be attained from competent field testing and the judgment of a licensed engineer."];
[self popupmaker :#"Soil Cohesion": #"Cohesion defines the non-stress dependent shear strength of soil and should be used with caution. Typically,cohesion occurs in stiff, over-consolidated clays or cemented native soils. Cohesion should be neglected if the designer is unsure of its presence."];
}
- (IBAction)SwitchDidChange:(id)sender {
if ([sender isOn]) {
self.cohesionUnits.text = #"Ft";
self.unitWeightUnits.text= #"M";
}else{
self.cohesionUnits.text = #"M";
self.unitWeightUnits.text = #"M";
}
}
- (IBAction)unitWtDidChange:(id)sender {
self.unitWeightValue.text = [NSString stringWithFormat:#"%.1f", (double)self.unitWeightStepper.value];
}
- (IBAction)frictionAngleDidChange:(id)sender {
self.frictionAngleValue.text = [NSString stringWithFormat:#"%d", (int)self.frictionAngleStepper.value];
}
- (IBAction)cohesionDidChange:(id)sender {
self.cohesionValue.text = [NSString stringWithFormat:#"%.1f", (double)self.cohesionStepper.value];
}
- (IBAction)textFieldDismiss:(id)sender {
[[self view] endEditing:YES];
}
- (IBAction)UnitSwitch:(id)sender {
if ([sender isOn]) {
self.unitWeightUnits.text = #"LBS/cubic Ft.";
self.cohesionUnits.text = #"imp";
}else{
self.unitWeightUnits.text = #"KG/m3";
self.cohesionUnits.text = #"met";
}
}
- (IBAction)cancelButton:(id)sender {
[self.navigationController popViewControllerAnimated:YES];
}
- (IBAction)saveButton:(id)sender {
Soil *thisSoil = self.thisSoil;
thisSoil.soilType = self.soilNameField.text;
NSLog(#"soil type field %#", self.soilNameField.text);
NSLog(#"soil type: %#", thisSoil.soilType);
thisSoil.frictionAngle = [self.frictionAngleValue.text integerValue];
if ([self.soilUnitsSwitch isOn] ) {
thisSoil.cohesion = [self.cohesionValue.text doubleValue];
thisSoil.unitWeight = [self.unitWeightValue.text doubleValue];
}else{
thisSoil.cohesion = [self.cohesionValue.text doubleValue];
thisSoil.unitWeight = [self.unitWeightValue.text doubleValue];
}
[self.delegate SoilCreatorViewController:self didFinishItem:thisSoil];
[self.navigationController popViewControllerAnimated:YES];
}
-(void)popupmaker:(NSString *)title :(NSString *)message{
UIAlertView * alert =[[UIAlertView alloc ] initWithTitle:title
message:message
delegate:self
cancelButtonTitle:#"OK"
otherButtonTitles:nil
];
[alert show];
}
#end
The .h file
#import "Soil.h"
#class SoilCreatorViewController;
#protocol SoilCreatorViewDelegate <NSObject>
-(void)SoilCreatorViewController:(SoilCreatorViewController *)controller didFinishItem:(Soil *)item;
#property (nonatomic, weak) id <SoilCreatorViewDelegate> delegate;
#end
#import <UIKit/UIKit.h>
#import "CalculationDetailViewController.h"
#interface SoilCreatorViewController : UIViewController
#property (nonatomic,weak) id<SoilCreatorViewDelegate> delegate;
#property (weak, nonatomic) IBOutlet UITextField *soilNameField;
#property (weak, nonatomic) IBOutlet UISwitch *soilUnitsSwitch;
#property (nonatomic,strong) Soil *thisSoil;
#property (weak, nonatomic) IBOutlet UIStepper *frictionAngleStepper;
#property (weak, nonatomic) IBOutlet UIStepper *unitWeightStepper;
#property (weak, nonatomic) IBOutlet UIStepper *cohesionStepper;
#property (weak, nonatomic) IBOutlet UILabel *unitWeightValue;
#property (weak, nonatomic) IBOutlet UILabel *frictionAngleValue;
#property (weak, nonatomic) IBOutlet UILabel *cohesionValue;
#property (weak, nonatomic) IBOutlet UILabel *unitWeightUnits;
#property (weak, nonatomic) IBOutlet UILabel *cohesionUnits;
- (IBAction)soilQuestions:(id)sender;
- (IBAction)SwitchDidChange:(id)sender;
- (IBAction)unitWtDidChange:(id)sender;
- (IBAction)frictionAngleDidChange:(id)sender;
- (IBAction)cohesionDidChange:(id)sender;
- (IBAction)textFieldDismiss:(id)sender;
- (IBAction)cancelButton:(id)sender;
- (IBAction)saveButton:(id)sender;
-(void)popupmaker:(NSString *)title :(NSString *)message;
#end
I don't see any code that sets up thisSoil. At some point in your code, you need to write something like
self.thisSoil = [[Soil alloc] init];
or self.thisSoil will stay nil forever.
I have a project that displays a feed of statuses similar to other social networks. Each feed item has several button that do different things. 1 of the buttons opens a new viewcontroller that displays the comments that have been posted on a particular status. When this button is clicked and the view controller is opened i would like it to be a push segue so that their is a back button and the user can navigate back to the feed.
When this button is clicked and the new vc is launched some unique data about the particular status/cell being clicked needs to be sent to the "comments vc ". Where would the code for doing this go?
CUSTOM CELL .H
#import <UIKit/UIKit.h>
#interface FeedItemCell : UITableViewCell
#property (weak, nonatomic) IBOutlet UIImageView *DefaultImg;
#property (weak, nonatomic) IBOutlet UILabel *NameLabel;
#property (weak, nonatomic) IBOutlet UILabel *StatusLabel;
#property (weak, nonatomic) IBOutlet UILabel *timeLabel;
#property (nonatomic, copy) NSString *msg_id;
#property (nonatomic, copy) NSString *status;
#property (nonatomic, weak) IBOutlet UIButton* commentButton;
#property (nonatomic, weak) IBOutlet UIButton* bumpButton;
#property (strong, nonatomic) id delegate;
-(IBAction)viewComments:(id)sender;
-(IBAction)bump:(id)sender;
#end
#protocol CustomCellProtocol <NSObject>
- (void)EBCellPressed:(NSString *)cellName;
CUSTOM CELL .M
#import "FeedItemCell.h"
#import "CommentsViewController.h"
#import "NSDate+TimeAgo.h"
#interface FeedItemCell() <WYPopoverControllerDelegate>
{
}
- (IBAction)open:(id)sender;
- (void)close:(id)sender;
#end
#implementation FeedItemCell
#synthesize commentButton;
- (instancetype)initWithDelegate:(id)delegate {
self = [super init];
if (self) {
self.delegate = delegate;
// Initialization code
}
return self;
}
-(IBAction)bump:(id)sender{
[self.delegate EBCellPressed:#"NAME"];
}
- (IBAction)open:(id)sender
{
}
#end
publicFeed . 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:#"www" 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://buhzhyve.com/CI_REST_LOGIN/UPLOADS/thumbs/";
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:#"buhz_mini_logo.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;
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;
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"commentSegue"]) {
}
}
#end
publicfeed .h
#import <UIKit/UIKit.h>
#interface PublicFeedViewController : UIViewController<UITableViewDataSource,UITableViewDelegate>
#property (weak, nonatomic) IBOutlet UITableView *FeedTable;
#property (weak, nonatomic) IBOutlet UIBarButtonItem *sidebarButton;
- (IBAction)addItem;
#end
So assuming that you're creating this button in code, this is how you could handle this.
This first line tells the button that when it's pressed, it needs to call this specific selector/method sent as the action.
[button addTarget:self action:#selector(showNextViewController) forControlEvents:UIControlEventTouchUpInside];
Then you would create this method in the same class.
- (void) showNextViewController
{
NewViewController *newViewController = [[NewViewController alloc] init]; //Edit this line of course to fit for your situation. I'm not sure if you're loading from an XIB or from a Storyboard, or neither.
newViewController.someVariable = someVariable;
newViewController.someOtherVariable = someOtherVariable;
[[[[[UIApplication sharedApplication] delegate] window] rootViewController].navigationController pushViewController:view animated:YES];
}
This will send the necessary data to the new view controller, and it will also display the new view on the screen with a back button.
Hope this works!
1. Ok so lets pretend this is your custom cell class.
in your .h file of the custom cell you need to add a protocol.
#import <UIKit/UIKit.h>
#interface CustomCell : UIView
#property (strong, nonatomic) id delegate; //this is used for sending messages out of the custom cell
//init
- (id)initWithFrame:(CGRect)frame andCatName:(NSString *)name andDelegate:(id)delegate;
#end
#protocol CustomCellProtocol <NSObject>
-(void)customCellSelected:(NSString *)cellName;
#end
what we actually did is something like creating a custom event that the class can throw out and everyone who subscribes to that can run a method when customCellSelected is thrown.
2. now when you create each custom cell with the init method you need to provide a delegate which kind of points to which class should the custom cell transfer the call to customCellSelected so in the init method you set that delegate.
- (id)initWithFrame:(CGRect)frame andDelegate:(id)delegate {
self = [super initWithFrame:frame];
if (self) {
self.delegate = delegate; //setting which class should be called when calling protocol methods.
// Your initialization code
}
return self;
}
3. now in your .m file of the custom cell, when the user presses your button and you enter your method , let it be buttonPressed
- (IBAction) buttonPressed:(id)sender {
[self.delegate customCellSelected:#"THE CELL'S NAME"]; // calling the protocol method.
}
now the call to the delegate method should be transferred to the vc because when you create the custom cell you use initWithFrame:(CGRect)frame andDelegate:(id)delegate and you transfer self as the delegate , so when [self.delegate customCellSelected:#"THE CELL'S NAME"]; is called it is actually called on the vc.
4. this is how you create the custom cell in the vc:
customCell *tempView = [[customCell alloc] initWithFrame:CGRectMake(X, Y, Width, Height) andDelegate:self]; // here you set the vc as the delegate
5.and now all you have to do is add the method customCellSelected to your vc code so it can called when the customCell is calling it.
- (void)customCellSelected:(NSString *)cellName {
self.selectedCell = cellName;
[self performSegueWithIdentifier:#"SelectedCell" sender:self];
}
6.then add this in the vc:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"SelectedCell"]) {
LevelSelectViewController *levelSelectViewController = (LevelSelectViewController *)segue.destinationViewController;
levelSelectViewController.cellName = self.selectedCell;
}
}
7.only thing you have to remember is to create a segue from your first vc to the second like this:
I have an array, players, with two strings inside it: player1 and player2. Here is my .h file:
#import <UIKit/UIKit.h>
#interface hardOne : UIViewController {
UISwitch *hard1ON;
NSMutableArray *players;
NSString *player1, *player2;
}
#property (nonatomic, retain) IBOutlet UISwitch *hard1ON;
#property (nonatomic) BOOL switchState;
#property (nonatomic, retain) NSMutableArray *players;
- (IBAction) switchValueChanged;
#end
The array is initialized in the viewDidLoad then the data is entered into that array in two IBActions in my .m file:
#import "hardOne.h"
#interface hardOne () <UITextFieldDelegate>
#property (nonatomic, strong) IBOutlet UITextField *textFieldOne;
#property (nonatomic, strong) IBOutlet UITextField *textFieldTwo;
#end
#implementation hardOne
#synthesize hard1ON;
#synthesize players;
#synthesize textFieldOne;
#synthesize textFieldTwo;
BOOL switchState;
int counter = 0;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
[hard1ON setOn:switchState animated:NO];
//read player names to user defaults
[textFieldOne setText:[[NSUserDefaults standardUserDefaults] stringForKey:#"player1"]];
[textFieldTwo setText:[[NSUserDefaults standardUserDefaults] stringForKey:#"player2"]];
self.players = [[NSMutableArray alloc] init];
NSLog(#"%#",self.players);
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction) switchValueChanged
{
counter += 1;
if (counter % 2 == 0) {
switchState = 0;
} else {
switchState = 1;
}
if (hard1ON.on) {
[[NSNotificationCenter defaultCenter] postNotificationName:#"theChange" object:nil];
} else {
[[NSNotificationCenter defaultCenter] postNotificationName:#"theChange2" object:nil];
}
}
- (IBAction) returnKey1
{
player1 = [textFieldOne text];
[self.players addObject:(player1)];
//set player1's name to user defaults
[[NSUserDefaults standardUserDefaults] setValue:[textFieldOne text] forKey:#"player1"];
}
- (IBAction) returnKey2
{
player2 = [textFieldTwo text];
[self.players addObject:(player2)];
//set player2's name to user defaults
[[NSUserDefaults standardUserDefaults] setValue:[textFieldTwo text] forKey:#"player2"];
}
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
[textField resignFirstResponder];
return NO;
}
#end
If I use NSLog in the second IBAction, once it is complete, the array is correctly displayed in the console with the strings player1 and player2, however if I try to use the array anywhere else it is null. Could anyone point me in the right direction?
You've got two definitions for players.
One is a property. It's never initialized and so it's null. You use it as self.players and backed by the instance variable _players.
One is an instance variable. It's initialized in viewDidLoad. It's not nil.
This is almost surely a mistake.
I would try adding the array as a private instance variable i.e add it to the .m file in the #interface with
NSMutableArray *players;
then you should be able to access the array just by using "players" instead of self.players. This should be then be available throughought the whole of your class. If this doesn't work then I would say the problem doesn't lie within the scope of your variable but rather the with some other code.
I have a a class I created to generate UIButton's I add to my UIView. This worked great until my conversion to ARC yesterday, not I get the following error:
-[OrderTypeButton performSelector:withObject:withObject:]: message sent to deallocated instance 0x12449f70
Here is the code to add the button to my UIView (actually a subview in my main UIView):
OrderTypeButton *btn = [[OrderTypeButton alloc]initWithOrderType:#"All Orders" withOrderCount:[NSString stringWithFormat:#"%i",[self.ordersPlacedList count]] hasOpenOrder:NO];
btn.view.tag = 6969;
btn.delegate = self;
[btn.view setFrame:CGRectMake((col * width)+ colspacer, rowHeight + (row * height), frameWidth, frameHeight)];
[self.statsView addSubview:btn.view];
And here is my class header:
#import <UIKit/UIKit.h>
#protocol OrderTypeButtonDelegate
-(void) tapped:(id)sender withOrderType:(NSString*) orderType;
#end
#interface OrderTypeButton : UIViewController {
id<OrderTypeButtonDelegate> __unsafe_unretained delegate;
IBOutlet UILabel *lblOrderType;
IBOutlet UILabel *lblOrderCount;
NSString *orderType;
NSString *orderCount;
BOOL hasOpenOrder;
}
#property (nonatomic, strong) IBOutlet UIButton *orderButton;
#property (nonatomic, strong) IBOutlet UILabel *lblOrderType;
#property (nonatomic, strong) IBOutlet UILabel *lblOrderCount;
#property (nonatomic, strong) NSString *orderType;
#property (nonatomic, strong) NSString *orderCount;
#property (nonatomic, assign) BOOL hasOpenOrder;
#property (nonatomic, unsafe_unretained) id<OrderTypeButtonDelegate> delegate;
-(id) initWithOrderType: (NSString *) anOrderType withOrderCount: (NSString *) anOrderCount hasOpenOrder: (BOOL) openOrder;
-(IBAction)btnTapped:(id)sender;
#end
Implementation:
#import "OrderTypeButton.h"
#implementation OrderTypeButton
#synthesize orderButton;
#synthesize lblOrderType, lblOrderCount, orderType, orderCount, hasOpenOrder, delegate;
-(id) initWithOrderType: (NSString *) anOrderType withOrderCount: (NSString *) anOrderCount hasOpenOrder: (BOOL) openOrder {
if ((self = [super init])) {
self.orderType = anOrderType;
self.orderCount = anOrderCount;
self.hasOpenOrder = openOrder;
}
return self;
}
- (void)didReceiveMemoryWarning
{
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
}
#pragma mark - View lifecycle
- (void)viewDidLoad
{
[super viewDidLoad];
self.lblOrderType.text =[NSString stringWithFormat:#"%#", self.orderType];
self.lblOrderCount.text = [NSString stringWithFormat:#"%#", self.orderCount];
if (self.hasOpenOrder) {
[self.orderButton setBackgroundImage:[UIImage imageNamed:#"background-order-btn-red.png"] forState:UIControlStateNormal];
self.lblOrderType.textColor = [UIColor whiteColor];
self.lblOrderCount.textColor = [UIColor whiteColor];
}
}
-(IBAction)btnTapped:(id)sender {
NSLog(#"TAPPED");
if ([self delegate] ) {
[delegate tapped:sender withOrderType:self.orderType];
}
}
- (void)viewDidUnload
{
[self setOrderButton:nil];
[super viewDidUnload];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return YES;
}
#end
This seems fairly simple what I am doing here, not sure what changed with ARC that is causing me problems.
Maybe ARC autorelease created button, try to store created buttons in Array
//.h file
#property (nonatomic, strong) NSArray *buttonsArray
//.m file
#synthesize buttonsArray
...
- (void)viewDidLoad {
buttonsArray = [NSArray array];
...
OrderTypeButton *btn = [[OrderTypeButton alloc]initWithOrderType:#"All Orders"
withOrderCount:[NSString stringWithFormat:#"%i",[self.ordersPlacedList count]]
hasOpenOrder:NO];
btn.view.tag = 6969;
btn.delegate = self;
[btn.view setFrame:CGRectMake((col * width)+ colspacer, rowHeight + (row * height), frameWidth, frameHeight)];
[self.statsView addSubview:btn.view];
//Add button to array
[buttonsArray addObject:btn];
Also this approach will help if you want to change buttons, or remove some specific button from view