How can I reuse a UIView and methods - DRY code in iOS - objective-c

I have 2 methods that add the Previous, Next & Done toolbar above the iOS Keyboard and handle these actions. I'm looking for a way to code these methods once and reuse it across multiple UITableViewControllers. (DRY Code)
I find myself copy and pasting these methods into each UITableViewController. If I make a small change, I have to copy and pastes that change everywhere. The code below is just an example, I seem to be repeating myself a lot in my code.
Here's an example of the code I'd like to reuse:
- (void) createInputAccessoryView
{
_inputAccView = [[UIView alloc] initWithFrame:CGRectMake(10,0,310,42)];
UIToolbar *keyboardToolbar = [[UIToolbar alloc] init];
keyboardToolbar.barStyle = UIBarStyleBlackTranslucent;
[keyboardToolbar sizeToFit];
_segmentedControl = [[UISegmentedControl alloc] initWithItems:[NSArray arrayWithObjects:#"Previous", #"Next", nil]];
[_segmentedControl setSegmentedControlStyle:UISegmentedControlStyleBar];
[_segmentedControl addTarget:self action:#selector(nextPrevious:) forControlEvents:UIControlEventValueChanged];
UIBarButtonItem *nextPrevButton = [[UIBarButtonItem alloc] initWithCustomView:_segmentedControl];
UIBarButtonItem *flexSpace = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:self action:nil];
UIBarButtonItem *doneBtn = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:#selector(resignKeyboard)];
NSArray *barItems = [NSArray arrayWithObjects:nextPrevButton, flexSpace, doneBtn, nil];
[keyboardToolbar setItems:barItems];
[_inputAccView addSubview:keyboardToolbar];
}
- (void) nextPrevious:(id) sender
{
switch(_activeTxtField.tag) {
case 1:
//Recipe Name
if (_segmentedControl.selectedSegmentIndex == 1){
[_descriptionTextField becomeFirstResponder];
_activeTxtField = _descriptionTextField;
}
break;
case 2:
//Recipe Description
if (_segmentedControl.selectedSegmentIndex == 0){
[_nameTextField becomeFirstResponder];
_activeTxtField = _nameTextField;
}
default:
break;
}
}

Create a custom UIView that defines the common input accessory view. Should include a definition of a delegate to allow the class using the accessory view to handle, for example, previous/next button taps as appropriate. Here's a header file example for a keyboard accessory view:
#import <UIKit/UIKit.h>
#class KeyboardAccessoryView;
#protocol KeyboardAccessoryViewDelegate <NSObject>
-(void)accessoryNext:(id)sender;
-(void)accessoryPrevious:(id)sender;
#end
#interface InputAccessoryView : UIView
#property (nonatomic, weak) id<KeyboardAccessoryViewDelegate> delegate;
#property (nonatomic, setter = enablePrevious:) BOOL previousEnabled;
#property (nonatomic, setter = enableNext:) BOOL nextEnabled;
-(id)initPreviousNextAccessory;
#end
Edit - showing details of use in a UIViewController.
The .h file:
#import <UIKit/UIKit.h>
#import "KeyboardAccessoryView.h"
#interface MyViewController : UIViewController <KeyboardAccessoryViewDelegate>
//...
#end
The .m file:
#import "MyViewController.h"
#interface MyViewController () {
KeyboardAccessoryView *inputAccessoryView;
}
#end
#implementation MyViewController
//...
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
inputAccessoryView = [[KeyboardAccessoryView alloc] initPreviousNextAccessory];
inputAccessoryView.delegate = self;
//...
}
-(void)accessoryNext:(id)sender{
// handle Next
}
-(void)accessoryPrevious:(id)sender{
// handle Previous
}
//...
#end

Related

What are the best practices for refactoring methods in Objective-C

How can I get this kind of method refactored better?
This is just a sample in my objective-c project, and I am trying to do it all programmatically
Im not sure what the best practices would be, create a protocol? an extension? or refactor further down to additional methods?
- (void)viewDidLoad {
[super viewDidLoad];
// Label to be changed
label = [[UILabel alloc]init];
label.text = #"Changed with code!";
[self.view addSubview:label];
// Label Constraints
label.translatesAutoresizingMaskIntoConstraints = NO;
[label.topAnchor constraintEqualToAnchor: self.view.safeAreaLayoutGuide.topAnchor constant:50].active = YES;
[label.leadingAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.leadingAnchor constant:20].active = YES;
[label.trailingAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.trailingAnchor constant:-20].active = YES;
}
As an example you could create a UIView subclass that creates the label and makes the layout.
#interface MyView: UIView
#property (nonatomic, strong, readonly) UILabel *label;
#end
#interface ViewController: UIViewController
#property (nonatomic, strong, readonly) MyView *myView;
#end
#implementation MyView
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
_label = [[UILabel alloc] initWithFrame:CGRectZero];
_label.translatesAutoresizingMaskIntoConstraints = NO;
UILayoutGuide *safeAreaLayoutGuide = self.safeAreaLayoutGuide;
[NSLayoutConstraint activateConstraints:#[
[label.topAnchor constraintEqualToAnchor: safeAreaLayoutGuide.topAnchor constant:50],
[label.leadingAnchor constraintEqualToAnchor:safeAreaLayoutGuide.leadingAnchor constant:20],
[label.trailingAnchor constraintEqualToAnchor:safeAreaLayoutGuide.trailingAnchor constant:-20]
]];
}
return self;
}
#end
#implementation MyViewController
- (void)loadView {
self.view = [[MyView alloc] initWithFrame:CGRectZero];
}
- (MyView *)myView {
return (id)self.view; // it makes view to load regardless which
// property do you use – `view` or `myView`
}
- (void)viewDidLoad {
[super viewDidLoad];
self.myView.label.text = #"Changed with code!";
}
#end

Custom input view controller

Is it possible to use UIInputViewController subclass as input view on iOS 9? I've tried setting inputViewController property but no custom view controller shows up when my text field becomes a first responder.
#interface InputViewController : UIInputViewController
#end
#implementation InputViewController
- (void)viewDidLoad {
[super viewDidLoad];
UIDatePicker *datePicker = [[UIDatePicker alloc] init];
datePicker.translatesAutoresizingMaskIntoConstraints = NO;
[self.inputView addSubview:datePicker];
[datePicker.leadingAnchor constraintEqualToAnchor:self.inputView.leadingAnchor].active = YES;
[datePicker.trailingAnchor constraintEqualToAnchor:self.inputView.trailingAnchor].active = YES;
[datePicker.topAnchor constraintEqualToAnchor:self.inputView.topAnchor].active = YES;
[datePicker.bottomAnchor constraintEqualToAnchor:self.inputView.bottomAnchor].active = YES;
}
#end
#interface TextField: UITextField
#property (nonatomic, readwrite, strong) UIInputViewController *inputViewController;
#end
#implementation TextField
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.textField.inputViewController = [[InputViewController alloc] init];
}
#end
Turned out the missing piece was:
self.view.translatesAutoresizingMaskIntoConstraints = NO;
Very frustrating that UIInputViewController does not do this automatically.

Trying to fix subview flipping and now subview buttons no longer work?

I have a view which includes two subviews. I had it working so that only one subview was shown at a time and each subview had a button and when the button was clicked the subview would flip over and the next subview would appear. The problem was that it appeared as though the entire view was flipping. After reading on this site about how to solve the problem I attempted to add the subviews to a container and flip that instead. However now, although my first subview is showing up when I press the button it no longer flip. It doesn't do anything. I put a log statement in the method which flips the subviews, as well as a breakpoint and as far as I can tell it no longer gets called. I'm very new to xcode and objective c and delegates and I have no idea how to proceed. Any help would be appreciated. Thanks.
I have included the relevant code here:
The header for the ViewController
#interface ExerciseViewController : UIViewController<ExerciseSubViewDelegate>
//stuff for subviews
#property (nonatomic, strong) ExerciseSubViewImage *subViewImage;
#property (nonatomic, strong) ExerciseSubViewText *subViewText;
#property UIView *panel;
#end
This is the code for the ViewController:
#interface ExerciseViewController ()
#end
#implementation ExerciseViewController
#synthesize subViewImage, subViewText;
- (void)viewDidLoad
{
self.subViewImage.delegate = self;
_panel = [[UIView alloc] initWithFrame:CGRectMake(0,0, self.view.bounds.size.width, self.view.bounds.size.height/2)];
_panel.backgroundColor = [UIColor whiteColor];
[self.view addSubview:_panel];
[_panel addSubview:subViewImage];
}
-(ExerciseSubViewImage *)subViewImage
{
if (!subViewImage)
{
CGRect subViewImageFrame = CGRectMake(0,0, _panel.bounds.size.width, _panel.bounds.size.height);
self.subViewImage = [[ExerciseSubViewImage alloc] initWithFrame:subViewImageFrame];
[_panel addSubview:subViewImage];
}
return subViewImage;
}
-(ExerciseSubViewText *)subViewText
{
if (!subViewText)
{
CGRect subViewTextFrame = CGRectMake(0,0, _panel.bounds.size.width, _panel.bounds.size.height);
self.subViewText = [[ExerciseSubViewText alloc] initWithFrame:subViewTextFrame];
self.subViewText.backgroundColor = [UIColor blueColor];
[_panel addSubview:subViewText];
}
return subViewText;
}
-(void)exerciseSubViewImagePressed
{
[UIView transitionWithView:_panel
duration:0.2
options:UIViewAnimationOptionTransitionFlipFromRight
animations:^{
[subViewImage removeFromSuperview];
[_panel addSubview:subViewText];
}
completion: nil];
//This is how I did it before I added the container
/*[UIView transitionFromView:subViewImage
toView:subViewText
duration:0.2
options:UIViewAnimationOptionTransitionFlipFromRight
completion:nil];
self.subViewText.delegate = self;*/
NSLog(#"Ipushedtheimage");
}
-(void)exerciseSubViewTextPressed
{//I haven't updated this yet
[UIView transitionFromView:subViewText
toView:subViewImage
duration:0.2
options:UIViewAnimationOptionTransitionFlipFromRight
completion:nil];
self.subViewImage.delegate = self;
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
subViewImage = nil;
subViewText = nil;
}
#end
This is the code for the delegate
#import
#protocol ExerciseSubViewDelegate <NSObject>
-(void) exerciseSubViewImagePressed;
-(void) exerciseSubViewTextPressed;
#end
I am also added the code for the first subview:
#import
#import "ExerciseSubViewDelegate.h"
#interface ExerciseSubViewImage : UIView
#property (nonatomic, strong) UIButton *button;
#property (nonatomic, assign) id<ExerciseSubViewDelegate>delegate;
#property (strong, nonatomic) UIImageView *exerciseImageView;
#end
#import "ExerciseSubViewImage.h"
#import "UIImage+animatedGIF.h"
#implementation ExerciseSubViewImage
#synthesize button;
#synthesize delegate;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
//Initialization code
self.button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
CGRect buttonFrame = CGRectMake(50,200,100,35);
self.button.frame = buttonFrame;
[self.button setTitle:#"Image"forState:UIControlStateNormal];
[self.button addTarget:self
action:#selector(buttonTouched)
forControlEvents:UIControlEventTouchUpInside];
[self addSubview:self.button];
_exerciseImageView = [[UIImageView alloc] initWithFrame:CGRectMake(50,20,160,158)];
NSURL *url = [[NSBundle mainBundle] URLForResource:#"AppleLogo" withExtension:#"gif"];
_exerciseImageView.image = [UIImage animatedImageWithAnimatedGIFURL:url];
[self addSubview:self.exerciseImageView];
}
return self;
}
-(void)buttonTouched
{
NSLog(#"imagebuttonpressed");
[self.delegate exerciseSubViewImagePressed];
}
Again, any help would be appreciate. I know I'm probably just not understanding something simple.
Ok. This took me all weekend but I finally figured it out on my own. I thought I would shere the answer here in case anyone else ever has a similar problem. After trying several other approaches I finally went back to the approach I used here and started inserting a whole bunch of NSLogs to determine the order that every thing was executing in. What I finally ended up doing was changing this: (all in the top ViewController)
self.subViewImage.delegate = self;
_panel = [[UIView alloc] initWithFrame:CGRectMake(0,0, self.view.bounds.size.width, self.view.bounds.size.height/2)];
_panel.backgroundColor = [UIColor whiteColor];
[self.view addSubview:_panel];
[_panel addSubview:subViewImage];
to this:
//create panel
_panel = [[UIView alloc] initWithFrame:CGRectMake(0,0, self.view.bounds.size.width, s self.view.bounds.size.height/2)];
_panel.backgroundColor = [UIColor whiteColor];
[self.view addSubview:_panel];
[_panel addSubview:subViewImage];
//Set the subview delegates
self.subViewImage.delegate = self;
self.subViewText.delegate = self;

How do I call a method from my uiviewcontroller in a utility nsobject class

How do I import a method from my uiviewcontroller to a utility class of nsobject (which I use in the viewcontroller to call common methods?) I have a large number of view controllers that need to call the same method.
Do I need to use NSInvocation? Should I use something different like protocols and delegates?
Without access to the method by the utility class I will just get "NSInvalidArgumentException"
This is the basic structure I need to use.
utilityClass - .h
#import <Foundation/Foundation.h>
#interface utilityClass : NSObject
- (void) swipeLoad: (UIView*)myview;
#end
.m
#import "utilityClass.h"
#implementation utilityClass
- (void) swipeLoad: (UIView*) myview
{
UISwipeGestureRecognizer *oneFingerSwipeRight =
[[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(oneFingerSwipeRight:)];
[oneFingerSwipeRight setDirection:UISwipeGestureRecognizerDirectionRight];
[myview addGestureRecognizer:oneFingerSwipeRight];
UISwipeGestureRecognizer *oneFingerSwipeLeft =
[[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(oneFingerSwipeLeft:)];
[oneFingerSwipeLeft setDirection:UISwipeGestureRecognizerDirectionLeft];
[myview addGestureRecognizer:oneFingerSwipeLeft];
UISwipeGestureRecognizer *twoFingerSwipeRight =
[[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(twoFingerSwiperight:)];
[twoFingerSwipeRight setDirection:UISwipeGestureRecognizerDirectionRight];
twoFingerSwipeRight.numberOfTouchesRequired = 2;
[myview addGestureRecognizer:twoFingerSwipeRight];
UISwipeGestureRecognizer *twoFingerSwipeLeft =
[[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(twoFingerSwipeLeft:)];
[twoFingerSwipeLeft setDirection:UISwipeGestureRecognizerDirectionLeft];
twoFingerSwipeLeft.numberOfTouchesRequired = 2;
[myview addGestureRecognizer:twoFingerSwipeLeft];
}
#end
view controller - .h
#import <UIKit/UIKit.h>
#import "utilityClass.h"
#class utilityClass;
#interface viewController : UIViewController
{
utilityClass *utilityClass;
}
#property (nonatomic, retain) utilityClass*utilityClass;
#end
.m
#import "viewcontroller.h"
#interface viewcontroller ()
#end
#implementation viewcontroller
#synthesize utilityClass = _utilityClass;
- (void)viewDidLoad
{
[super viewDidLoad];
self.utilityClass = [[utilityClass alloc] init];
[self.utilityClass swipeLoad:self.view];
}
- (void)twoFingerSwiperight:(UISwipeGestureRecognizer *)recognizer {
//[self another_method]
}
- (void)twoFingerSwipeLeft:(UISwipeGestureRecognizer *)recognizer {
//[self another_method]
}
- (void)oneFingerSwipeRight:(UISwipeGestureRecognizer *)recognizer {
//[self another_method]
}
- (void)oneFingerSwipeLeft:(UISwipeGestureRecognizer *)recognizer {
//[self another_method]
}
#end
It sounds like you would be better off subclassing UIViewController and putting your swipe methods in there. Then make all of your view controllers a subclass of your new subclass (in the example below, your view controllers would be subclasses of MyViewController.
You would then get rid of your utility class altogether.
// MyViewController.h
#import <UIKit/UIKit.h>
#implementation MyViewController : UIViewController
#end
// MyViewController.m
#implementation MyViewController
- (void)viewDidLoad {
[super viewDidLoad];
UISwipeGestureRecognizer *oneFingerSwipeRight =
[[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(oneFingerSwipeRight:)];
[oneFingerSwipeRight setDirection:UISwipeGestureRecognizerDirectionRight];
[self.view addGestureRecognizer:oneFingerSwipeRight];
UISwipeGestureRecognizer *oneFingerSwipeLeft =
[[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(oneFingerSwipeLeft:)];
[oneFingerSwipeLeft setDirection:UISwipeGestureRecognizerDirectionLeft];
[self.view addGestureRecognizer:oneFingerSwipeLeft];
UISwipeGestureRecognizer *twoFingerSwipeRight =
[[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(twoFingerSwiperight:)];
[twoFingerSwipeRight setDirection:UISwipeGestureRecognizerDirectionRight];
twoFingerSwipeRight.numberOfTouchesRequired = 2;
[self.view addGestureRecognizer:twoFingerSwipeRight];
UISwipeGestureRecognizer *twoFingerSwipeLeft =
[[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(twoFingerSwipeLeft:)];
[twoFingerSwipeLeft setDirection:UISwipeGestureRecognizerDirectionLeft];
twoFingerSwipeLeft.numberOfTouchesRequired = 2;
[self.view addGestureRecognizer:twoFingerSwipeLeft];
}
- (void)oneFingerSwipeRight:(UISwipeGestureRecognizer *)recognizer {
// Your code here
}
- (void)oneFingerSwipeLeft:(UISwipeGestureRecognizer *)recognizer {
// Your code here
}
- (void)twoFingerSwiperight:(UISwipeGestureRecognizer *)recognizer {
// Your code here
}
- (void)twoFingerSwipeLeft:(UISwipeGestureRecognizer *)recognizer {
// Your code here
}
#end
Also, one side note. It's recommended that you start class names with a capital letter, and start variable and functions with a lowercase letter. It helps make your code much more readable.

EXC_BAD_ACCESS in UIWebView delegate

I've got a problem - I'm getting EXC_BAD_ACCESS when trying to set UIWebView.delegate = self;
My code:
vkLogin.h -
#import UIKit/UIKit.h
#interface vkLogin : UIViewController <UIWebViewDelegate>
{
UIWebView *authBrowser;
UIActivityIndicatorView *activityIndicator;
}
#property (nonatomic, retain) UIWebView *authBrowser;
#property (nonatomic, retain) UIActivityIndicatorView *activityIndicator;
#end
vkLogin.m -
#import "vkLogin.h"
#import "bteamViewController.h"
#implementation vkLogin
#synthesize authBrowser;
- (void) viewDidLoad
{
[super viewDidLoad];
activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
activityIndicator.center = CGPointMake(self.view.bounds.size.width / 2, self.view.bounds.size.height / 2);
activityIndicator.autoresizesSubviews = YES;
activityIndicator.hidesWhenStopped = YES;
[self.view addSubview: activityIndicator];
[activityIndicator startAnimating];
authBrowser = [[UIWebView alloc] initWithFrame:self.view.bounds];
authBrowser.delegate = self;
authBrowser.scalesPageToFit = YES;
[self.view addSubview:authBrowser];
NSString *authLink = #"http://api.vk.com/oauth/authorize?client_id=-&scope=audio&redirect_uri=http://api.vk.com/blank.html&display=touch&response_type=token";
NSURL *url = [NSURL URLWithString:authLink];
[authBrowser loadRequest:[NSURLRequest requestWithURL:url]];
}
- (void) webViewDidFinishLoad:(UIWebView *)authBrowser
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Lol" message:#"OLOLO" delegate:self cancelButtonTitle:#"Okay" otherButtonTitles:nil, nil];
[alert show];
}
#end
So, if i'm commeting delegate string - everything working fine, but I didn't recieve my webViewDidFinishLoad event.
What I'm doing wrong?
The error isn't in the code you have posted. Your zombie message is saying your reference to vkLogin is bad. So you need to look at whatever class creates and holds a reference to your vkLogin class.
That class should be doing something like a vkLogin *foo = [[vkLogin alloc] init];
Update:
Based on your comments it looks like you are creating a local variable for vkLogin. It would be most useful to see the code creates and uses vkLogin and how it's called. Barring that, here are a few guesses.
You are called the method which creates and adds vkLogin to a subView more than once. (Each time would create a new instance).
You have some sort of call back which can occur after vkLogin has been removed.
My guess is vkLogin should be a property in your class, not a local method variable.
in your .h you would add
#proprerty (strong, nonatomic) vkLogin *vk;
and in your .m file you could refer to it as self.vk so you'd create it and add it as a subview like:
self.vk = [[vkLogin alloc] init];
[self.view addSubview:self.vk];
On a side note, convention says we should start class names with a capital letter, so you'd name the class VkLogin which would make it easily distinguishable from a variable named vkLogin (but worry about that after you solve the problem)