I know that this question was asked before many times but I didn't find the solution for my problem. I am not new at iOs and this should be pretty trivial stuff, but it is driving me mad and crazy :). Ok I have class C that is subclass of UIViewController. In it'S view there is a UIScrollView and in that scroll view I put class B and A also subclasses of UIViewController and I do it like this:
a = [[A alloc] initWithNibName:#"A" bundle:nil];
[a setDelegate:self];
[self addChildViewController:a];
[a.view setFrame:CGRectMake(2*vwMainScroller.frame.size.width, 0, a.view.frame.size.width, a.view.frame.size.height)];
[vwMainScroller addSubview:a.view];
b = [[B alloc] initWithNibName:#"B" bundle:nil];
[b setDelegate:self];
[self addChildViewController:b];
[b.view setFrame:CGRectMake(vwMainScroller.frame.size.width, 0, b.view.frame.size.width, speedScreen.view.frame.size.height)];
[vwMainScroller addSubview:b.view];
Both class A and class B have buttons on their views added programatically in viewDidLoad method like this:
- (void)viewDidLoad
{
[super viewDidLoad];
UIButton *btnChangeColor = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 200, 460)];
[btnChangeColor addTarget:self action:#selector(Btn) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:btnChangeColor];
}
-(void)Btn
{
NSLog(#"I am the selector and I have been called");
}
But when I press class A button the selector is never called, but when I press class B button the selector is called just like it should be.
I tried:
putting buttons on xibs
enabling user interaction for every view in my code - it was already enabled -> didn't help
painting buttons to see if they are where they should be -> they were
restarting computer and xCode
running on device and simulator
Nothing helped, always the same thing, button works on class B but not on class A, after I tried to add some other buttons in IB of class A, after I found out that they also don't call their selectors I checked the box "Shows touches on highlight" and guess what, they didn't glow when I touched them. So please I see that something is eating my touch events but just don't have any ideas how to proceed in debugging this matter. Please help me :)...
Try set up vwMainScroller.delaysContentTouches = Yes on your scroll view.
Add
[a.view setUserInteractionEnabled:YES];
and
[b.view setUserInteractionEnabled:YES];
before you add it as a subview to your scrollView, If it doesn't help set up your view controller as a delegate of your UIScrollView and add this:
-(BOOL)touchesShouldCancelInContentView:(UIView *)view
{
return ![view isKindOfClass:[UIButton class]];
}
Ok, stop thinking, cause that is what U need to do to solve this problem. I stopped thinking and created a new class "Anew", copied all code from class A, created exactly the same xib, by copying elements from A.xib, deleted class A and implemented Anew in exactly the same way I did with class A. And now everything is working as it should. So the answer is when U spend 4 hours on trying to solve something as trivial as this, and u tried every possible approach that U can imagine and that others suggested, and nothing helps, it is time to stop thinking and start deleting cause the problem is probably deeper than U can reach...
Related
I am new to iOS development and I am currently reading the book : iOS Programming (Objective C) by Big Nerd Ranch.
I am confused as in where to initialize subviews such as UIButtons, UIImageView while creating views programtically:
Should the intialization be done in the Main UIView i.e in the
initWithFrame method and maintain a additional weak reference to the subview in the UIView.
or
should I do it in the UIViewControllers loadView method and maintain a weak reference to the subview in the uiviewcontroller (Same approach used while creating UIVew using the interface builder).
I have seen both the approaches being used in various stackoverflow posts but no post that explains which approach is the right one.
you can initialize as per your app's requirement. If any view or button or anything is part of initial setup of your app then you should initialize it in viewDidload.
Now, for example there is requirement like user press button and then new view will be created then you can initialize view in button's click method etc.
So, it's depends on your requirement.
Static views which will live from start to and of app should be initialize in viewdidload, because this is the first method getting called of viewcontroller.
hope this will help :)
It dependes on which architecture you are using. Apple raises the flag of Model-View-Controller, but in fact, UIViewControllers are the View.
For Example:
Let's say that you have a pretty LoginViewController. When you instantiate it, you will be doing something like
LoginViewController *loginVC = [[LoginViewController alloc] init];
At this point, no view is loaded. Your ViewController has just executed the init method, nothing else. When the system calls
loginVC.view
the first method to be executed will be
- (void)loadView;
there you should do exactly that, load your view. So, the approach i like is to have an additional LoginView.
- (void)loadView
{
// you should have a property #property (nonatomic, strong) LoginView *loginView;
self.loginView = [[LoginView alloc] init];
self.view = self.loginView;
}
and in the LoginView init method, you should put your code to build up the view.
However, you could eliminate LoginView, and instantiate all your subviews like this:
- (void)loadView
{
self.view = [[UIView alloc] init];
UIButton *button = [[UIButton alloc] initWithTargetBlaBlaBla...];
[self.view addSubview:button];
// add more fancy subviews
}
In my experience, the first approach is much cleaner than the second one. It also makes version control a lot easier (try to merge a xib, I dare you). I always use MyView.m to build the view (a.k.a setup constriants, style) and use MyViewController.m things like animations, lifeCycle. I like to think that MyView.m is the programatic xib, so anything that you can do with xibs, you should me able to do it inside your view.
Hope it helps!!
I've added a sub view like this in the main view:
BTLPXYPad *XYPad = [[BTLPXYPad alloc] initWithFrame: CGRectMake (30, 10, 280, 460)];
[window addSubview:XYPad];
done all my bits that i need to and then removed it using this in the BTLPXYPad class:
[self removeFromSuperview];
What I need is to perform a task once it has gone. I know that with a UIViewController type class I could use viewDidDissapear but I can't seem to find the same thing for a UIView Type. Can anyone help please?
To know when you a view has actually been removed you could implement didMoveToSuperview and check if the superview is now nil
- (void)didMoveToSuperview;
{
[super didMoveToSuperview];
if (!self.superview) {
NSLog(#"Removed from superview");
}
}
[self removeFromSuperview];
What I need is to perform a task once it has gone.
When you say [self removeFromSuperview], it is gone. There is a delay of just one runloop for it to look gone, but removing the view removes the view.
So the solution is just to proceed to your "task once it is gone":
[self removeFromSuperview];
// do your task
If it seems like the "do your task" code is in the wrong place - it isn't. The fact that you need the view to be notified when it is gone shows that your architecture was wrong to start with. A view shouldn't be performing any "task"; it is View in the Model-View-Controller structure. It just displays stuff. Your view controller is Controller; it is the one to do the task.
Nor should the Controller need to consult the view at this point, because the view should not have been storing any important data to begin with. Data is Model, and should already have been retrieve and stored by the Controller before this moment.
Being new to objective-C coding I started out writing a basic app, fully programmatically (not using storyboards or xib) in one file, my AppViewController h and m files.
Everything worked lovely.
So then I wanted to break up the mass of code by subclassing sections, and everything went well apart from the UIPickerView. In fact simply commenting out the [background addSubview:colorPicker]; seemed to totally fix the issue. I never found the answer online so I proceeded to make a new document to replicate said issue.
So here goes:
UIPickerViewController.h
#import <UIKit/UIKit.h>
#import "Picker.h"
#interface UIPickerViewController : UIViewController
#end
Simply imports my new class.
UIPickerViewController.m
#import "UIPickerViewController.h"
#interface UIPickerViewController ()
#end
#implementation UIPickerViewController
- (void)viewDidLoad
{
[super viewDidLoad];
UIView *superview = self.view;
int height = superview.bounds.size.height;
int width = superview.bounds.size.width;
CGRect popupRect = CGRectMake(0, 0, width, height);
UIView *popup = [[UIView alloc]initWithFrame:popupRect];
popup.tag = 8;
[superview addSubview:popup];
Picker *picker = [[Picker alloc]initWithFrame:popupRect];
[picker viewAddTypeScreenToView:superview];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}
#end
Sets up a new view with a tag (so that i could reference it later with my new class)
Then actions a method from my new class to populate my new view.
Picker.h
#import <UIKit/UIKit.h>
#interface Picker : UIView
<UIPickerViewDataSource,UIPickerViewDelegate>
{
UIPickerView *colorPicker;
NSMutableArray *colorsArray;
}
#property (nonatomic, retain) UIPickerView *colorPicker;
#property (nonatomic, retain) NSMutableArray *colorsArray;
#property (strong,nonatomic) UILabel *myValue;
-(void)viewAddTypeScreenToView:(UIView*)superview;
#end
Setting up my variables and accessible method.
Picker.m
#import "Picker.h"
#implementation Picker
#synthesize colorsArray;
#synthesize colorPicker;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
}
return self;
}
-(void)viewAddTypeScreenToView:(UIView*)superview
{
UIView *baseView =[superview viewWithTag:8];
int height = baseView.bounds.size.height;
int width = baseView.bounds.size.width;
CGRect fullScreen = CGRectMake(0, 0, width, height);
UIView *background = [[UIView alloc]initWithFrame:fullScreen];
background.backgroundColor = [UIColor blackColor];
colorsArray = [[NSMutableArray alloc] initWithObjects:#"Red",#"Blue",#"Yellow",#"Green",nil];
CGRect myPickerRect = CGRectMake(10, 70, (width/2)-40, 200);
colorPicker = [[UIPickerView alloc]initWithFrame:myPickerRect];
colorPicker.dataSource = self;
colorPicker.delegate = self;
colorPicker.showsSelectionIndicator = YES;
[colorPicker selectRow:2 inComponent:0 animated:YES];
CGRect labelFrame = CGRectMake(10, 10, 180, 50);
_myValue = [[UILabel alloc]initWithFrame:labelFrame];
_myValue.textColor = [UIColor redColor];
_myValue.text = #"select colour";
[background addSubview:_myValue];
[background addSubview:colorPicker];
[baseView addSubview:background];
}
-(NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView{
return 1;
}
-(NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component{
return colorsArray.count;;
}
-(NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component
{
return colorsArray[row];
}
-(void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component{
_myValue.text = [NSString stringWithString:colorsArray[row]];
}
#end
And finally the initiation called by the method in the picker class file.
This gives me an error along these lines
-[UITableViewCellContentView pickerView:titleForRow:forComponent:]: unrecognized selector sent to instance 0x8f2b000
2014-03-19 10:29:48.407 Briefcase[1800:60b] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[UITableViewCellContentView pickerView:titleForRow:forComponent:]: unrecognized selector sent to instance 0x8f2b000'
Which i've read is to do with either the datasource, or ARC systems, however none of the responses that I have found relate to or work with the type of set up that I have above. I'm sure it's something really simple but after a few days of failed searching, it's officially driving me crazy.
The problem is most likely that the instance of Picker that is being created in UIPickerViewController is never added to the view hierarchy and thus gets released prematurely (provided we're talking about a project using ARC here).
This leads to the pickerview's delegate and datasource becoming invalid and, basically, pointing at any random object. That's what is causing your crash: A message to your delegate cannot be delivered because the delegate is dead already. The picker still keeps a pointer which used to point at the delegate, but which has become invalid and points at a random object now, in this case a table view cell, which basically doesn't know what to do with this message and crashes.
The problem should go away if you add Picker *picker as an ivar or a retaining / strong property to UIPickerViewController.h - this will retain the picker beyond the scope of the viewDidLoad method and should keep it alive.
But that would be just a workaround, the real problem is your overall design. You said you're new to objective-c and indeed, it looks like you lack a basic understanding of iOS view and view controller hierarchies and, to some degree, the concept of object oriented programming. You might want to dig into something more basic before trying to fix your code because, quite frankly, it should be rather re-written than fixed.
I'd be happy to provide you with suggestions about how to structure your code, but please provide some information about what functionality you'd like to achieve first.
Edit (in response to your comment):
As a rule of thumb, do not spread functionality over several classes unless necessary. For objects, which serve a rather infrastructural purpose, like a specialized textfield or a pickerview, always ask yourself: "If I would like to reuse that object in another project, would that be as easy as using any other existing object, like, for example, UILabel?" If the answer is "No", then something is wrong. Ideally, interface objects are self-contained and to use them, you just invoke them, add them to a view and tell them, which text to display or which options to offer. If that information is subject to change or if the object needs to interact with other parts of your code, make use of delegation and protocols. Under no circumstances should the functionality of your object be tied to hard coded values or rely to some view to have a certain tag.
If you subclass UIView, the resulting object should behave like any other instance of UIView. It should be added to the view hierarchy by you or some object, but it shouldn't add or remove itself. If it works without being added to the view hierarchy at all, something is wrong. A view serves the purpose of being a part in your interface and all the logic it contains should work to that end, not more, not less.
Normally, interface objects should not interfere with one another. If something happens to one object (button pressed, option selected, text changed...) and another object is supposed to reflect that change, it is the view controllers responsibility to make that happen. The view controller is the place where the logic happens. If there is a task which requires a lot of complex logic, it might be a good idea to encapsule that logic into a purpose build class. One such example would be a class which manages network connections. This class should be again self contained: If the view controller needs some remote information, it asks your network class. Once your network class has that information (or failed to retrieve it), it reports back to your view controller. The view controller then updates the interface - under no circumstance should the networking class contain code which affects the interface.
It is important to understand that you could very well ignore these rules and still end up with a working app. And in some cases, the "direct" way may appear to be easier to implement and thus may look very tempting. But you'll pay the price later - once you start debugging your code. If your picker does not behave the way it should, you need to look into several places and wrap your mind around several objects, just to make one interface object behave right. And likely you will break one functionality while fixing the other.
So, try to make it right from the start, even though it requires more planning and learning. Trust me, it pays out, I started out just like you several years ago ;)
I hope you can help me with the next case.
Description:
I have a class that "creates" a dynamic form programmatically, this class inherits from UIViewController and is called "DinamicScreen".
#interface DinamicScreen : UIViewController ...
I have a ViewController (connected to a viewController storyboard), which inherits from "DinamicScreen".
#interface MandatoryInformationViewController : DinamicScreen<UIPickerViewDelegate,UITextFieldDelegate>
In "DinamicScreen" I'm only painting the UIView, but all delegates are passed to the context.
uiTextField = [[UILabel alloc]initWithFrame:....
// Context is "MandatoryInformationViewController" in this case
[uiTextField setDelegate:context];
The form implements a UIScrollView containing UILabels and UITextFields. An UITextField can open an ActionSheet showing an UIPickerView and two buttons (OK, Cancel).
DinamicScreenScrollView *uiScrollView = [[DinamicScreenScrollView alloc]initWithFrame...
[uiScrollView addSubview:uiTextField];....
The problem:
When I click on a "pickerTextField" the ActionSheet appears without problems, but when I click OK or Cancel, it crashes (I think it is the ActionSheet).
When I did a debug, I observed that the flow does not run correctly
[actionSheet dismissWithClickedButtonIndex:0 animated:YES];
The snapshot http://s29.postimg.org/8zxkj333b/Captura_de_pantalla_2013_12_17_a_la_s_17_45_38.png
If I do not implement the UIScrollView it works perfectly, but its a must for it to be implemented.
If more information is needed please let me know, it's my first question on StackOverflow
Excuse me for my English, I'm working on it.
Thanks.
Well, after three days to fix it.
The problem was that were painting in the view of "MandatoryViewController" instead of "DinamicScreen", I changed that and voila.
[[context view] addSubview:uiScrollView]; // [wrong]
[[self view] addSubview:uiScrollView]; // [well]
I supposed that this post are a documentation now.
Thanks again.
So I'm having problems releasing some view controllers.
In essence the dealloc for the PhotoPostViewController never seems to get called, so I can't clear down the images contained within that are munching all the memory.
This is my UIViewController subclass, I can have up to 100 of these at any one time added as subviews to the main scroll view, the iPad gets tight for memory after that.
#interface PhotoPostViewController : UIViewController {
IBOutlet UIImageView *backgroundImage;
IBOutlet UIImageView *serviceImage;
}
Then in my main view class I have a method to create these views and add them to a scrollView. This method is typically called from a loop to create all the subviews I need.
- (void) addPost {
PhotoPostViewController *postView = [[PhotoPostViewController alloc] initWithNibName:#"PhotoPostViewController" bundle:nil];
[scrollView addSubview:[postView view]];
[viewControllers addObject:postView];
}
viewControllers is an NSMutableArray created in the main class init.
scrollView is a UIScrollView on my main view.
This all works fine, I know the limit of the memory usage on the iPad and keep within that at any given time, opening Popovers to give preview images and videos etc...
Doesn't run out of memory until I try to refresh the screen.
The code to do this is:
- (IBAction)didPressRefresh:(id)sender {
for(UIView *subview in [scrollView subviews]) {
[subview removeFromSuperview];
}
for(UIViewController *c in viewControllers) {
[c release];
}
[viewControllers removeAllObjects];
}
For the sake of simplicity I clear off all the subviews and try to release them before recreating the next set of subviews using the function above.
It removes them from the view, but runs out of memory adding the new set of view controllers. In my test cases the sets of view controllers are identical in content, so if it loads from clean first time, then it should load the second time and every other time after that if I release everything properly.
What actually happens is it crashes due to low memory when creating the second set of view controllers.
While debugging I've put breakpoints on the 'viewDidUnload' and 'dealloc' methods, but they never get hit.
It looks like the UIViewController itself is getting released, yet the UIImageViews within are not, clearly they'd usually get released by my code in the dealloc (or viewDidUnload) method.
So I'm confused.
Counting things it looks to me like the reference counts are fine. so how come the dealloc is not getting hit ?
Andi
You need to send the postView object the -release message after adding it to the viewControllers collection:
- (void) addPost {
PhotoPostViewController *postView = [[PhotoPostViewController alloc] initWithNibName:#"PhotoPostViewController" bundle:nil];
[scrollView addSubview:[postView view]];
[viewControllers addObject:postView];
[postView release];
}
The reason why you need to do this is because the collection sends the -retain message to all objects that are added to it, hence the memory leak and -dealloc not being hit.
EDIT:
Your -didPressRefresh: method should look like this:
- (IBAction)didPressRefresh:(id)sender {
[[scrollView subviews] makeObjectsPerformSelector:#selector(removeFromSuperview)];
[viewControllers removeAllObjects];
}