AutoLayout without usage of Storyboards or Interface Builder - ios

I am building an app where I want to completely avoid using Storyboard and Interface Builder in general, so all UI should be specified in code. I am using PureLayout, a nice API for configuring AutoLayout constraints.
However, my issue is that it seems like AutoLayout is disabled when not using Interface Builder. updateViewConstraints, the method where I put the layout according to the recommendation given by the PureLayout author, is not called at all.
Just to give a bit more info about my setup:
deleted Main.storyboard and removed the entry from my Info.plist
manually setup self.window in AppDelegate.m and added UINavigationController with MainMainController as rootViewController
As mentioned, my main issue is that updateViewConstraints does not get called on MainViewController but the UI elements are all displayed with the frames that I passed to them during initilization.
Note: It seems to me like I just need to enable some flag somewhere to mimic the checkbox from Interface Builder with which you can indicate whether you want to use AutoLayout.
MainViewController.m
#interface MainViewController ()
#property (nonatomic, strong) UIButton *startButton;
#property (nonatomic, assign) BOOL didSetupConstraints;
#end
#implementation MainViewController
- (void)viewDidLoad
{
[super viewDidLoad];
[self.view addSubview:self.startButton];
[self.view setTranslatesAutoresizingMaskIntoConstraints:NO];
}
- (UIButton *)startButton
{
if (!_startButton) {
UIButton *startButton = [UIButton buttonWithType:UIButtonTypeSystem];
CGRect startButtonFrame = CGRectMake(75.0, 75.0, 250.0, 44.0);
startButton.frame = startButtonFrame;
[startButton setTitle:#"Start" forState:UIControlStateNormal];
[startButton setTranslatesAutoresizingMaskIntoConstraints:NO];
_startButton = startButton;
}
return _startButton;
}
- (void)updateViewConstraints
{
NSLog(#"Update view contraints");
if (!self.didSetupConstraints) {
[self.startButton autoCenterInSuperview];
self.didSetupConstraints = YES;
}
[super updateViewConstraints];
}
#end

Related

Button is disabled when adding background

I am using Parse anypic tutorial and I want to create a somehow different UI. But I have some troubles.
So, my ViewController is this :
#interface PAPHomeViewController ()
#property (nonatomic, strong) PAPSettingsActionSheetDelegate *settingsActionSheetDelegate;
#property (nonatomic, strong) UIView *blankTimelineView;
#end
#implementation PAPHomeViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(#"LoadView is called");
// Present Anypic UI
[self presentUI];
}
-(void) presentUI {
/*UIImageView *backgroundImageView = [[UIImageView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]];
[backgroundImageView setImage:[UIImage imageNamed:#"DefaultAnypic.png"]];
self.view = backgroundImageView;*/
// Settings button
self.settingsButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[self.settingsButton addTarget:self
action:#selector(settingsButtonAction:)
forControlEvents:UIControlEventTouchUpInside];
[self.settingsButton setTitle:#"Settings" forState:UIControlStateNormal];
self.settingsButton.frame = CGRectMake(200.0, 200.0, 100.0, 100.0);
[self.view addSubview:self.settingsButton];
}
and its .h file is this :
#interface PAPHomeViewController : PAPPhotoTimelineViewController
#end
The PAPPhotoTimelineViewController is also a separateViewController, which the Home extends from and it is a tableViewController, which also calls ViewDidLoad.
The problem :
With the above Code, I see my button and I can click on my button.
But, if I uncomment stuff for the background, I do see the background, I do see the button, but it cannot be clicked - it is like no touch on the button is identified.
I am also confused, now that I am extending another ViewController which also implements viewDidLoad, why they are both called, in which order etc.
You shouldn't assign a new UIView to your self.view.
Instead of self.view = backgroundImageView;, just add it like a random view.
[self.view addSubview:backgroundImageView];
Doing that, you will follow the right way to add subview: your backgroundImageView will be displayed in your view, and your button will be add above it.

ARC and autoreleased object

I need a ViewController to be called modally to show some UIButton and other UIView on top of the current window. I want the background to be partially transparent and showing the current window below it - something similar to a UIActionSheet but with a custom design. I coded my VC to do the following: 1) during init the VC sets self.view.frame equals to [[UIApplication sharedApplication]keyWindow].frame 2) when show() is called the VC adds self.view on top of [[UIApplication sharedApplication]keyWindow] subViews 3) when an internal button calls the private method release() the VC remove self.view from its superview. Example with a single release button as follows:
#implementation ModalController
- (id)init
{
self = [super init];
if (self){
//set my view frame equal to the keyWindow frame
self.view.frame = [[UIApplication sharedApplication]keyWindow].frame;
self.view.backgroundColor = [UIColor colorWithWhite:0.3f alpha:0.5f];
//create a button to release the current VC with the size of the entire view
UIButton *releaseMyselfButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[releaseMyselfButton setTitle:#"Release" forState:UIControlStateNormal];
releaseMyselfButton.frame = CGRectMake(0, 0, 90, 20);
[releaseMyselfButton addTarget:self action:#selector(releaseMyself) forControlEvents:UIControlEventTouchDown];
//add the button to the view
[self.view addSubview:releaseMyself];
}
return self;
}
- (void) show
{
//add self.view to the keyWindow to make sure that it will appear on top of everything else
[[[UIApplication sharedApplication]keyWindow] addSubview:self.view];
}
- (void)releaseMyself
{
[self.view removeFromSuperview];
}
#end
If I create an instance of ModalController from another VC and I call show() everything goes as expected:
#interface CurrentVC ()
#property (strong, nonatomic) ModalController *myModalController;
#end
#implementation CurrentVC
- (void)viewDidLoad
{
[super viewDidLoad];
self.myModalController = [[ModalController alloc]init];
[self.myModalController show];
}
#end
To make it work I need to retain the ModalController in a property until release () is called. However I would like to have the same freedom I have with UIActionSheet and simply keep an instance of it in a local variable:
#implementation CurrentVC
- (void)viewDidLoad
{
[super viewDidLoad];
ModalController *myModalController = [[ModalController alloc]init];
[myModalController show];
}
#end
If I do this with the current code ARC will release myModalController straight after show() is called and the release button will be pointing to nil. How can I make this work without storing the object in a property? I've identified a work around but I'm not sure it's a good design option:
#interface ModalController ()
#property (strong, nonatomic) ModalController *myselfToAutorelease;
#implementation ModalController
- (id)init
{
self = [super init];
if (self){
... ... ...
self.myselfToAutorelease = self;
}
return self;
}
- (void) show
{
... ... ...
}
- (void)releaseMyself
{
[self.view removeFromSuperview];
self.myselfToAutorelease = nil;
}
What I've done is making ModalController "self sufficient" - it stores a pointer to itself during init and set it to nil when it's ready to release himself. It works but I have the feeling that this is against the ARC design principles! Is this approach correct? If not, how can I handle this differently?
Thanks in advance for your help.
Doesn't work like that.
You don't keep a reference to self.
In the main view controller you just create your object. If you need it to be around longer keep it in a property in the main view controller , when done, set the property to nil in the main view controller.

Trigger a method in UIViewController from its View

I have a UIViewController with its UIView which contains a UIButton. I want to trigger a method in UIViewController on button click event.
Keeping reference of UIViewController doesn't seem to be a good idea like the following link says:
Get to UIViewController from UIView?
So I want to achive this using a delegate. Any hint on how to achieve this?
You can do something like this
CustomView.h
#import <UIKit/UIKit.h>
#protocol CustomViewDelegate <NSObject>
-(void)didButtonPressed;
#end
#interface CustomView : UIView
#property (assign) id<CustomViewDelegate> delegate;
#end
CustomView.m
#import "CustomView.h"
#implementation CustomView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
self.backgroundColor = [UIColor whiteColor];
//[self addSubview:titleLbl];
UIButton *button= [UIButton buttonWithType:UIButtonTypeRoundedRect];
button.frame = CGRectMake(100, 100, 100, 50);
[button addTarget:self.delegate action:#selector(didButtonPressed) forControlEvents:UIControlEventTouchUpInside];
[button setTitle:#"pressMe" forState:UIControlStateNormal];
[self addSubview:button];
}
return self;
}
in your ViewController.m
-(void)loadView
{
[super loadView];
CustomView *view = [[CustomView alloc]initWithFrame:self.view.bounds];
view.delegate = self;
[self.view addSubview:view];
}
This is what the responder chain was built for. When you add a target to your button, just supply nil for the target:
[mySpecialButton addTarget:nil
action:#selector(mySpecialButtonTapped:)
forControlEvents:UIControlEventTouchUpInside];
The nil target basically means "send mySpecialButtonTapped: to any object in the responder chain that can handle it".
Now you can handle this selector anywhere in the responder chain, which includes the button itself, its containing view, its containing view controller, the UIApplication, and finally your AppDelegate. Just place this method in the object most appropriate for your needs:
- (void)mySpecialButtonTapped:(id)sender {
NSLog("My special button was tapped!");
}
You don't need delegates or callback blocks (as in the accepted answer) if you just want to bubble a message up.
I guess that you expected something more fundamental then just pass some button action to controller.
I always follow MVC pattern in case of model/view/controller collaboration. It resolve your issue and many other. And I want to share my experience.
Separate controller from view and model: don't put all of the "business logic" into view-related classes; this makes the code very unusable. Make controller classes to host this code, but ensure that the controller classes don't make too many assumptions about the presentation.
Define callback APIs with #protocol, using #optional if not all the methods are required.
For view define protocol like <view class name>Protocol (example NewsViewProtocol). For controller define delegate like <view class name>Delegate (example NewsViewDelegate) and dataSource like <view class name>DataSource (example NewsViewDataSource). Keep all this #protocols in one separate file named <view class name>Protocol.h (example NewsViewProtocol.h)
Short example:
Contents of NewsView.h
//
// NewsView.h
#interface NewsView : UIView <NewsViewProtocol> {
#protected
NSObject* delegate_;
NSObject* dataSource_;
}
#end
Contents of NewsController.h and .m
//
// NewsController.h
#interface NewsController : UIViewController <NewsViewDataSource, NewsViewDelegate> {
}
#property (nonatomic, weak) UIView<NewsViewProtocol>* customView;
#end
#implementation NewsController
- (void)viewDidLoad {
[super viewDidLoad];
self.customView = (UIView<NewsViewProtocol>*)self.view;
[self.customView setDelegate:self];
[self.customView setDataSource:self];
}
#end
Contents of NewsViewProtocol.h
//
// NewsViewProtocol.h
#protocol NewsViewProtocol;
#protocol NewsViewDelegate<NSObject>
#optional
- (void)someAction;
- (void)newsView:(UIView<NewsViewProtocol>*)newsView didSelectItemAtIndexPath:(NSIndexPath *)indexPath;
#end
#protocol NewsViewDataSource<NSObject>
#required
- (id)newsView:(UIView<NewsViewProtocol>*)newsView itemAtIndexPath:(NSIndexPath *)indexPath;
- (NSInteger)numberOfItemsInNewsView:(UIView<NewsViewProtocol>*)newsView section:(NSInteger)section;
- (BOOL)newsView:(UIView<NewsViewProtocol>*)newsView shouldDisplaySection:(NSInteger)section;
#end
#protocol NewsViewProtocol<NSObject>
#required
//Never retain delegate instance into implementation of this method
- (void)setDelegate:(NSObject<NewsViewDelegate>*)delegate;
//Never retain delegate instance into implementation of this method
- (void)setDataSource:(NSObject<NewsViewDataSource>*)dataSource;
- (void)reload;
#end
You may consider that it is redundant. In simple view controller, YES. But if you develop very complex screen with huge amount of data then it gives you some advantages as:
Helps you to separate responsibility between view and controller.
Keeps your code clear.
Makes you code more reusable.
Life is easy in xCode.
At the very beginning be sure that your xib View (the one with your button inside it) is associated to the right ViewController class. Which can be the default ViewController class that comes with a new project or your custom one.
After this, here comes the magic trick! Separate your view into 2 panel. The goal is to see your xib and your viewController code (the .m file). Now press the control key of your keyboard and drag your UIButton to the code. Select IBAction. It will generate something you can call a "listener" in other language. Go to the core code of your View Controller and complete the method!
Easy as that! Have fun :)
You don't really need delegates for this - it is how UIButtons are intended to be used. Just control-click and drag from your button to the .m file for your UIViewController. This will create a new method. From there, you can either make a call to the method you wrote or just copy-paste what you have into the new method.
You can try this:
[yourButton addTarget:self action:#selector(yourButtonAction:) forControlEvents:UIControlEventTouchUpInside];
And in your selector specify the action
- (IBAction)yourButtonAction:(id)sender {
//Action to perform
}
To add a button programmatically, in myViewController.m
UIView *yourView = [[UIView alloc] init];
UIButton *yourButton = [[UIButton alloc] initWithFrame:CGRectMake(0,0,100,21)];
[yourButton addTarget:self action:#selector(yourMethod) forControlEvents:UIControlEventTouchDown];
[yourView addSubview:yourButton];
More info here.

Send a Delegate message from UIPopover to Main UIViewController

I'm trying to use a Button in my UIPopover to create a UITextView in my Main UIViewController the code I have looks something like this (PopoverView.h file):
#protocol PopoverDelegate <NSObject>
- (void)buttonAPressed;
#end
#interface PopoverView : UIViewController <UITextViewDelegate> { //<UITextViewDelegate>
id <PopoverDelegate> delegate;
BOOL sendDelegateMessages;
}
#property (nonatomic, retain) id delegate;
#property (nonatomic) BOOL sendDelegateMessages;
#end
Then in my PopoverView.m file:
- (void)viewDidLoad
{
[super viewDidLoad];
UIButton * addTB1 = [UIButton buttonWithType:UIButtonTypeRoundedRect];
addTB1.frame = CGRectMake(0, 0, 100, 50);
[addTB1 setTitle:#"Textbox One" forState:UIControlStateNormal];
[self.view addSubview:addTB1]; // Do any additional setup after loading the view from its nib.
[addTB1 addTarget:self action:#selector(buttonAPressed)
forControlEvents:UIControlEventTouchUpInside];
}
- (void)buttonAPressed
{
NSLog(#"tapped button one");
if (sendDelegateMessages)
[delegate buttonAPressed];
}
And also in my MainViewController.m :
- (void)buttonAPressed {
NSLog(#"Button Pressed");
UITextView *textfield = [[UITextView alloc] init];
textfield.frame = CGRectMake(50, 30, 100, 100);
textfield.backgroundColor = [UIColor blueColor];
[self.view addSubview:textfield];
}
I'm using a delegate protocol to link the popover and the ViewController but I'm stuck on how I get my BOOL statement to link the -(void)buttonAPressed in the PopoverView and MainViewController so that when I press the button in the Popover a textview appears in the Main VC. How would I go about doing this?
In MainViewController, where you create PopoverView, be sure to set its delegate property otherwise sending messages to delegate in PopoverView will do nothing.
For example, in MainViewController.m:
PopoverView *pov = [[PopoverView alloc] initWithNibName:nil bundle:nil];
pov.delegate = self; // <-- must set this
thePopoverController = [[UIPopoverController alloc] initWithContent...
I am not sure why you need the sendDelegateMessages variable. Even with that bool, you must set the delegate property so PopoverView has an actual object reference to send the messages to.
If you want to make sure the delegate object has implemented the method you're about to call, you can do this instead:
if ([delegate respondsToSelector:#selector(buttonAPressed)])
[delegate buttonAPressed];
Also, the delegate property should be declared using assign (or weak if using ARC) instead of retain (see Why use weak pointer for delegation? for an explanation):
#property (nonatomic, assign) id<PopoverDelegate> delegate;
Another thing is if you're not using ARC, you need to add [textfield release]; at the end of the buttonAPressed method in MainViewController to avoid a memory leak.

Accessing UIButton from another class not working?

I am trying to access my UIButton in my Play class from my CCLayerClass.
The problem is that it is not working!
Here is how I declare it in the Play class:
.h
IBOutlet UIButton *pauseButton;
#property(nonatomic, retain) IBOutlet UIButton *pauseButton;
.m
#synthesize pauseButton;
Then in the dealloc:
[pauseButton release];
Also of course I connect it then in Interface builder.
Then in my other class (My CCLayer) class. I try to do this:
Play *play = [[[Play alloc] init] autorelease];
[play.pauseButton setHidden:YES];
The thing is, is that it simply just does not hide the button. Is there any reason for this?
Thanks!
Edit1:
My Play.h
IBOutlet UIButton *pauseButton;
BOOL pauseButtonVisible;
#property(nonatomic, retain) IBOutlet UIButton *pauseButton;
#property(readwrite) BOOL pauseButtonVisible;
.m
#synthesize pauseButton;
- (void)setPauseButtonVisible: (BOOL) variableToSet {
pauseButtonVisible = variableToSet;
if(pauseButton)
[pauseButton setHidden: !pauseButtonVisible];
}
- (BOOL) pauseButtonVisible
{
return(pauseButtonVisible);
}
viewWillAppear:
[pauseButton setHidden: !pauseButtonVisible];
I also made sure I connected it in Interface Builder
Then in CCLayerClass I do this:
Play *play = [[[Play alloc] init] autorelease];
if(play.pauseButton == NULL) {
NSLog( #"pause button is NULL");
}
But that NSLog gets called! Why is my pauseButton NULL? I just need to alloc it so it stays alive, is that possible?
Thanks!
play.pauseButtonVisible = YES;
Okay. Hopefully third time is the charm (and after that, I'm giving up cause it's time for me to go to bed).
Here in the .h file, I'm keeping the new pauseButtonVisible BOOL property.
#interface Play : UIViewController
{
BOOL pauseButtonVisible;
IBOutlet UIButton *pauseButton;
}
#property(nonatomic, retain) IBOutlet UIButton *pauseButton;
#property(readwrite) BOOL pauseButtonVisible;
#end
But in the .m file, we're doing something a little different:
#interface Play
// here we are rolling our own setters and getters
// instead of #synthesizing...
- (void)setPauseButtonVisible: (BOOL) variableToSet
{
pauseButtonVisible = variableToSet;
if(pauseButton)
[pauseButton setHidden: !pauseButtonVisible];
}
- (BOOL) pauseButtonVisible
{
return(pauseButtonVisible);
}
- (void) viewWillAppear: (BOOL) animated
{
[pauseButton setHidden: !pauseButtonVisible];
[super viewWillAppear: animated];
}
and
Play *play = [[[Play alloc] init] autorelease]; // you should really be using initWithNibName, but anyways
play.pauseButtonVisible = YES;
So now, hopefully pause button will be visible or hidden at the appropriate time for while your code is running.

Resources