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.
Related
I have very simple UIView that creating the box, but what is happen is the UIView does not show at all, here is my code on sharingButtons.m
-(void)createContainer{
winWidth = [UIScreen mainScreen].bounds.size.width;
buttonContainer = [[UIView alloc] initWithFrame:CGRectMake(0, 0, winWidth, 20)];
buttonContainer.backgroundColor = [UIColor redColor];
[self.view addSubview:buttonContainer];
}
-(void)createButton{
[self createContainer];
}
and here is my sharingButtons.h
#interface SocialSharing : UIViewController {
int winWidth;
}
- (void)createButton;
- (void)createContainer;
#pragma mark - Properties
#property(nonatomic, strong) UIView* buttonContainer;
#end
And createButton method is called from MyViewControler.m on viewDidLoad
Is any wrong with my code??
EDITED
Here is my code on MyViewControler.m
- (void)loadSocialSharingButton {
socialButtons = [[SocialSharing alloc] init];
[socialButtons createButton];
}
- (void)viewDidLoad {
[super viewDidLoad];
[self loadSocialSharingButton];
}
Sorry I just learn about obj c :)
Thanks a lot
The reason your buttonContainer is not visible is, it is not loaded in your view hierarchy.
To make it visible you should add it as subview. In MyViewController.m in viewDidLoad add the following line after [self loadSocialSharingButton];
[self.view addSubview:socialButtons.buttonContainer];
Hope this Helps!
Your SocialSharing is subclass of UIViewController.
And you add your buttonContainer view to this SocialSharing Controller,this controller is not on screen if you just call
socialButtons = [[SocialSharing alloc] init];
[socialButtons createButton];
So,you can not see anything.
In an iOS App, only one ViewController at a time is active. And as you are at MyViewController, so MyViewController is active, if you want to navigate to any other view controller than you need to present or push the instance of same. Doing so will make the other view controller as active.
In your case the problem is your SocialSharing is a subclass of UIViewController as it is created as SocialSharing : UIViewController and it's not active, so adding any view over it won't be visible as the instance of SocialSharing is not pushed/ presented. If you need to show the view from SocialSharing than either you subclass it from UIView or push/ present the instance of SocialSharing to make it's view active and visible.
You are currently # MyViewController and But you are loading your custom view # SocialSharing ViewController, Both ViewController are distinct and you can't just get your custom view at social sharing to MyViewController by initializing it.
You have change SocialSharing class as sub class of UIView and initialize this view and add to subview of MyViewController.
SocialSharing.h
#interface SocialSharing : UIView {
int winWidth;
}
- (instancetype)createButton;
#pragma mark - Properties
#property(nonatomic, strong) UIView* buttonContainer;
#end
SocialSharing.m
- (instancetype)createButton
{
winWidth = [UIScreen mainScreen].bounds.size.width;
self = [super initWithFrame:CGRectMake(0, 0, winWidth, 20)];
if (self) {
buttonContainer = [[UIView alloc] initWithFrame:];
buttonContainer.backgroundColor = [UIColor redColor];
[self addSubview:buttonContainer];
}
return self;
}
MyViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
[self loadSocialSharingButton];
}
- (void)loadSocialSharingButton {
socialButtons = [SocialSharing alloc] createButton];
[self.view addSubView:socialButtons];
}
I am creating UIViewController programmatically. In its viewDidLoad, I am creating instance of custom UIView Component which has bunch of button and text filed. I set this view as viewControllers view.
But when I select any UIButton it does not fire any event or tapping inside UITextFiled does not bring up keyboard either.
- (void)viewDidLoad
{
[super viewDidLoad];
SomeView *sv = [[SomeView alloc] initWithFrame:self.view.bounds];
self.view.userInteractionEnabled = YES;
self.view = sv;
//[self.view addSubView:sv];
self.title = #"Something";
}
Is there anything wrong with this piece of code ? Even adding subivew does not work.
I added tapgesture to sv and It was getting detected but nothing else was getting selected on view.
I tried added buttons/textfiled to the viewcontrollers view directly. Then it works but not through customview component.
Try holding a strong reference to the view, It might be getting deallocated.
#interface YOURCLASS ()
#property (nonatomic, strong) SomeView *sv;
#end
And then Go back fro trying to add it as a subview. See if that works.
- (void)viewDidLoad
{
[super viewDidLoad];
self.sv = [[SomeView alloc] initWithFrame:self.view.bounds];
self.view.userInteractionEnabled = YES;
[self.view addSubView:self.sv];
self.title = #"Something";
}
I have 2 view controller,
FirstViewController - > SecondViewController via
[self presentViewController:SVC animated:YES completion:nil];
on SecondViewContrller when I do
[self dismissViewControllerAnimated:YES completion:nil];
My question is, Why is the objects not release on secondViewController after I dismiss this viewcontroller. As you can see on the graph It didn't go down after dismiss. BTW whats the best way to release/dismiss a ViewController?
[EDIT]
I NSLog a message on dealloc method on every VC, When I start from FVC->SVC->[dismiss SVC]. this is my logs
This can be pretty rough stuff. I had similar issues before. Search your code and see if you have strong or wrong references to objects.
One of my top mistakes (and what I have seen on the internet hundreds of times) are delegate properties. I wrote them like #property (nonatomic, retain) id<protocol>delegate; for quite a long time as I realized that if I do so, the delegated object does not get released. One have to use assign in this case.
Hope that help you...
I have made some investigation with this behavior.
FirstViewController.m
#import "FirstViewController.h"
#import "SecondViewController.h"
#interface FirstViewController ()
#end
#implementation FirstViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
-(void)dealloc {
NSLog(#"First Dealloc");
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.view.backgroundColor = [UIColor redColor];
UIButton *pressMe=[UIButton buttonWithType:UIButtonTypeCustom];
pressMe.frame = CGRectMake(0, 0, 100, 40);
pressMe.center = self.view.center;
[pressMe setTitle:#"PressMe" forState:UIControlStateNormal];
[pressMe addTarget:self action:#selector(pressMeAction:)
forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:pressMe];
// Do any additional setup after loading the view.
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
-(void) pressMeAction:(id) sender
{
SecondViewController *svc = [[SecondViewController alloc] init];
[self presentViewController:svc animated:YES completion:nil];
NSLog(#"Present Second");
}
#end
SecondViewController.m
is pretty the same except
-(void) pressDissmissButtonAction:(id) sender
{
[self dismissViewControllerAnimated:YES completion:nil];
NSLog(#"Dismiss Second");
}
and this is Allocation dynamics
As you can see after pressMeButtonAction invoked secondViewController allocated and after pressDissmissButtonAction invoked secondViewController is successfully deallocated.
BUT: Most of the time it deallocated immediately, but if you present and dismiss it very quickly (twice a second or so), dellocation not fired immediately, but after a while.
I Assume that this is by design implementation of ARC deallocation procedure. Not sure.
try this ...
[self presentViewController:SVC animated:YES completion:nil];
SVC = nil;
After spending many hours on this, I finally found a missing piece of the puzzle: Not only do you have to set any strong references to the ViewController to nil, you also have to invalidate any timers and be aware of block retain cycles.
Any time you use self in a block you create a retain cycle! Instead you should declare a variable like so
__unsafe_unretained ViewController *weakSelf = self;
and use it instead of self in the block.
Check all IBOutlets in your application. There might be "strong" property assigned to them. Make them "weak". For example, an IBOulet should be like this:
#property (weak, nonatomic) IBOutlet UILabel *myLabel;
Check all delegates (if any) in your application. Every delegate should be like this:
#property (nonatomic, assign) id <yourProtocol> delegate;
Note that, it takes some amount of time for ARC to recover memory.
Timers were the issue in my case. Added timer invalidate to viewWillDisappear and the view controllers were then released.
This may sound like a simple question, but yet I can't find an answer for it anywhere.
I am switching between views and everything is fine. I can pass variables and data between views, but the problem arises when I want to go back to a previous view.
Any data on the view is gone and deleted as if it hadn't been passed through. Of course, I don't want the data to unload when I switch to a new view. Is this possible?
This is how I am switching between views in my project. Is there a better way?
TicketStart *backPage=[[TicketStart alloc]initWithNibName:#"TicketStart" bundle:nil];
[self addChildViewController:backPage];
[self.view addSubview:backPage.view];
When I search this question all I can find are questions about passing things through or loading new views. Sorry if this question has been asked, but I can not find an answer anywhere.
I'm not sure what triggers your views to change, so you'll have to write that code.
Parent
#implementation ParentViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
self.vcOne = [[OneViewController alloc] init];
[self addChildViewController:self.vcOne];
[self.view addSubView:self.vcOne.view];
self.vcTwo = [[TwoViewController alloc] init];
}
-(void)switchViews {
//logic to animate in view that isn't present
}
#end
View Controller One
#interface OneViewController ()
#property(strong, nonatomic) UIView *vOne;
#end
#implementation OneViewController
-(UIView*)vOne {
if(_vOne == nil) {
_vOne = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, self.view.frame.size.width, self.view.frame.size.height)];
}
return _vOne;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
[self.view addSubView:self.vOne];
}
#end
View Controller Two
#interface TwoViewController ()
#property(strong, nonatomic) UIView *vTwo;
#end
#implementation TwoViewController
-(UIView*)vTwo {
if(_vTwo == nil) {
_vTwo = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, self.view.frame.size.width, self.view.frame.size.height)];
}
return _vTwo;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
[self.view addSubView:self.vTwo];
}
#end
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.