My Views Hierarchy is next:
My root View Controller that contains custom view
My custom UIView that contain custom UITableView
My custom UITableView
And i want to use delegate methods of my custom UITableView, but i don't really know how to access it's delegates because UITableView is added in my custom UIView class. Please clear me up, i'm little confused about this question.
Here's my code:
Adding my custom UITableView to UIView(i'm using CollapseClick):
-(void) initMehod{
DetailsView *testView = [[[NSBundle mainBundle] loadNibNamed:#"DetailsView" owner:self options:nil] lastObject];
test1View = testView;
//collapse click init
CGRect viewRect = [test1View frame];
viewRect.origin.x = 10;
test1View.frame = viewRect;
[test1View setBackgroundColor:[UIColor magentaColor]];
self.myCollapseClick.CollapseClickDelegate = self;
[self.myCollapseClick reloadCollapseClick];
// If you want a cell open on load, run this method:
[self.myCollapseClick openCollapseClickCellAtIndex:0 animated:NO];
}
...
And i'm using PagedFlowView in my root View Controller to add CustomView:
- (UIView *)flowView:(PagedFlowView *)flowView cellForPageAtIndex:(NSInteger)index{
mainDetailV = (MainDetailView *)[flowView dequeueReusableCell];
if (!mainDetailV) {
mainDetailV = [[[NSBundle mainBundle] loadNibNamed:#"MainDetailView" owner:self options:nil] lastObject];
//mainDetailV.layer.cornerRadius = 6;
mainDetailV.layer.masksToBounds = YES;
}
[usedViewControllers insertObject:mainDetailV atIndex:index];
return mainDetailV;
}
Everything works perfectly, except i can't use my custom UITableView delegate (for example didSelectRowAtIndexPath: method).
EDIT: I have fixed my problem, PagedFlowView actually handles tag gesture recogniser and overrides my custom tableView delegate methods.
Similar question:
UITableView as a subview?
First of all your collapsable tableview abstracts tableview delegates (i.e its a wrapper).
It provides you with a method didClickCollapseClickCellAtIndex instead. Use that.
It seems to be that you have some problem in understanding delegation pattern.
Avoid breaking the cascading effect of delegation pattern. It makes debugging easy
and helps to understand the flow of the code. Below diagram might help you to understand what I mean
Implement you didSelectRowAtIndexPath in custom view instead of rootViewcontroller.
// This is your custom view
-(id)init{
----
self.myCollapseClick.CollapseClickDelegate = self;
----
}
//collapsable delegate
-(void)didClickCollapseClickCellAtIndex:(int)index isNowOpen:(BOOL)open {
// you need to create your own custom view delegate with mehthod didSelectIndex:index
[self.customViewDelegate didSelectIndex:index];
}
In your rootviewcontroller you need to do this
customView.customViewDelegate = self;
-(void)didSelectIndex:(int)index{
// do your stuff here
}
Related
I am aware this question has been asked before but the answers are contradicting and I am confused, so please don't flame me.
I want to have a reusable UIView subclass throughout my app. I want to describe the interface using a nib file.
Now let's say it's a loading indicator view with an activity indicator in it. I would like on some event to instantiate this view and animate in to a view controller's view. I could describe the view's interface no problem programmatically, creating the elements programmatically and setting their frame inside an init method etc.
How can I do this using a nib though? Maintaining the size given in interface builder without having to set a frame.
I've managed to do it like this, but I'm sure it is wrong (it's just a view with a picker in it):
- (id)initWithDataSource:(NSDictionary *)dataSource {
self = [super init];
if (self){
self = [[[NSBundle mainBundle] loadNibNamed:[NSString stringWithFormat:#"%#", [self class]] owner:self options:nil] objectAtIndex:0];
self.pickerViewData = dataSource;
[self configurePickerView];
}
return self;
}
But I'm overwriting self, and when I instantiate it:
FSASelectView *selectView = [[FSASelectView alloc] initWithDataSource:selectViewDictionary];
selectView.delegate = self;
selectView.frame = CGRectMake(0, self.view.bottom + 50, [FSASelectView width], [FSASelectView height]);
I have to manually set the frame rather than have it picked up from IB.
EDIT: I want to create this custom view in a view controller, and have access to control the view's elements. I don't want a new view controller.
Thanks
EDIT: I Don't know if this is best practice, I'm sure it's not, but this is how I did it:
FSASelectView *selectView = [[[NSBundle mainBundle] loadNibNamed:[NSString stringWithFormat:#"%#",[FSASelectView class]] owner:self options:nil] objectAtIndex:0];
selectView.delegate = self;
[selectView configurePickerViewWithData:ds];
selectView.frame = CGRectMake(0, self.view.bottom + 50, selectView.width, selectView.height);
selectView.alpha = 0.9;
[self.view addSubview:selectView];
[UIView animateWithDuration: 0.25 delay: 0 options:UIViewAnimationOptionAllowUserInteraction |UIViewAnimationOptionCurveEaseInOut animations:^{
selectView.frame = CGRectMake(0, self.view.bottom - selectView.height, selectView.width, selectView.height);
selectView.alpha = 1;
} completion:^(BOOL finished) {
}];
Correct practice still wanted
Should this have been done using a view controller and init with nib name? Should I have set the nib in some UIView initialisation method in the code? Or is what I have done ok?
MyViewClass *myViewObject = [[[NSBundle mainBundle] loadNibNamed:#"MyViewClassNib" owner:self options:nil] objectAtIndex:0]
I'm using this to initialise the reusable custom views I have.
Note that you can use "firstObject" at the end there, it's a little cleaner. "firstObject" is a handy method for NSArray and NSMutableArray.
Here's a typical example, of loading a xib to use as a table header. In your file YourClass.m
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
return [[NSBundle mainBundle] loadNibNamed:#"TopArea" owner:self options:nil].firstObject;
}
Normally, in the TopArea.xib, you would click on File Owner and set the file owner to YourClass. Then actually in YourClass.h you would have IBOutlet properties. In TopArea.xib, you can drag controls to those outlets.
Don't forget that in TopArea.xib, you may have to click on the View itself and drag that to some outlet, so you have control of it, if necessary. (A very worthwhile tip is that when you are doing this for table cell rows, you absolutely have to do that - you have to connect the view itself to the relevant property in your code.)
If you want to keep your CustomView and its xib independent of File's Owner, then follow these steps
Leave the File's Owner field empty.
Click on actual view in xib file of your CustomView and set its Custom Class as CustomView (name of your custom view class)
Add IBOutlet in .h file of your custom view.
In .xib file of your custom view, click on view and go in Connection Inspector. Here you will all your IBOutlets which you define in .h file
Connect them with their respective view.
in .m file of your CustomView class, override the init method as follow
-(CustomView *) init{
CustomView *result = nil;
NSArray* elements = [[NSBundle mainBundle] loadNibNamed: NSStringFromClass([self class]) owner:self options: nil];
for (id anObject in elements)
{
if ([anObject isKindOfClass:[self class]])
{
result = anObject;
break;
}
}
return result;
}
Now when you want to load your CustomView, use the following line of code
[[CustomView alloc] init];
Follow the following steps
Create a class named MyView .h/.m of type UIView.
Create a xib of same name MyView.xib.
Now change the File Owner class to UIViewController from NSObject in xib. See the image below
Connect the File Owner View to your View. See the image below
Change the class of your View to MyView. Same as 3.
Place controls create IBOutlets.
Here is the code to load the View:
UIViewController *controller=[[UIViewController alloc] initWithNibName:#"MyView" bundle:nil];
MyView* view=(MyView*)controller.view;
[self.view addSubview:myview];
Hope it helps.
Clarification:
UIViewController is used to load your xib and the View which the UIViewController has is actually MyView which you have assigned in the MyView xib..
Demo
I have made a demo grab here
Answering my own question about 2 or something years later here but...
It uses a protocol extension so you can do it without any extra code for all classes.
/*
Prerequisites
-------------
- In IB set the view's class to the type hook up any IBOutlets
- In IB ensure the file's owner is blank
*/
public protocol CreatedFromNib {
static func createFromNib() -> Self?
static func nibName() -> String?
}
extension UIView: CreatedFromNib { }
public extension CreatedFromNib where Self: UIView {
public static func createFromNib() -> Self? {
guard let nibName = nibName() else { return nil }
guard let view = NSBundle.mainBundle().loadNibNamed(nibName, owner: nil, options: nil).last as? Self else { return nil }
return view
}
public static func nibName() -> String? {
guard let n = NSStringFromClass(Self.self).componentsSeparatedByString(".").last else { return nil }
return n
}
}
// Usage:
let myView = MyView().createFromNib()
In Swift:
For example, name of your custom class is InfoView
At first, you create files InfoView.xib and InfoView.swiftlike this:
import Foundation
import UIKit
class InfoView: UIView {
class func instanceFromNib() -> UIView {
return UINib(nibName: "InfoView", bundle: nil).instantiateWithOwner(nil, options: nil)[0] as! UIView
}
Then set File's Owner to UIViewController like this:
Rename your View to InfoView:
Right-click to File's Owner and connect your view field with your InfoView:
Make sure that class name is InfoView:
And after this you can add the action to button in your custom class without any problem:
And usage of this custom class in your MainViewController:
func someMethod() {
var v = InfoView.instanceFromNib()
v.frame = self.view.bounds
self.view.addSubview(v)
}
Well you could either initialize the xib using a view controller and use viewController.view. or do it the way you did it. Only making a UIView subclass as the controller for UIView is a bad idea.
If you don't have any outlets from your custom view then you can directly use a UIViewController class to initialize it.
Update: In your case:
UIViewController *genericViewCon = [[UIViewController alloc] initWithNibName:#"CustomView"];
//Assuming you have a reference for the activity indicator in your custom view class
CustomView *myView = (CustomView *)genericViewCon.view;
[parentView addSubview:myView];
//And when necessary
[myView.activityIndicator startAnimating]; //or stop
Otherwise you have to make a custom UIViewController(to make it as the file's owner so that the outlets are properly wired up).
YourCustomController *yCustCon = [[YourCustomController alloc] initWithNibName:#"YourXibName"].
Wherever you want to add the view you can use.
[parentView addSubview:yCustCon.view];
However passing the another view controller(already being used for another view) as the owner while loading the xib is not a good idea as the view property of the controller will be changed and when you want to access the original view, you won't have a reference to it.
EDIT: You will face this problem if you have setup your new xib with file's owner as the same main UIViewController class and tied the view property to the new xib view.
i.e;
YourMainViewController -- manages -- mainView
CustomView -- needs to load from xib as and when required.
The below code will cause confusion later on, if you write it inside view did load of YourMainViewController. That is because self.view from this point on will refer to your customview
-(void)viewDidLoad:(){
UIView *childView= [[[NSBundle mainBundle] loadNibNamed:#"YourXibName" owner:self options:nil] objectAtIndex:0];
}
I have a MainWindowViewController with the proper protocols. I also have the dataSouce methods implemented in MainWindowViewController.
#interface MainWindowController : UIViewController < UITableViewDelegate, UITableViewDataSource, UAModalPanelDelegate, UIGestureRecognizerDelegate>
I have the delegate and dataSource set in the viewDidLoad of MainWindowViewController.
self.friendsTableView.delegate = self;
self.friendsTableView.dataSource = self;
What should happen is that I press the friends button. A xib file name FriendsPopUpView_iPhone is loaded and it should bring up a UITableView of friends. But the friendsPopUpView's tableview shows up with empty rows. What am I doing wrong?
FriendsPopUpView_iPhone.xib contains a UITableView.
friendsTableView is an outlet from the tableview created in FriendsPopUpView_iPhone.xib.
friendsPopUpView is a UIView outlet for the the view in FriendsPopUpView_iPhone.xib.
Here is the action connected to the friend button on the main MainWindowController.
- (IBAction)on_friends:(id)sender {
if (self.friendsPopUpView == nil) {
[[NSBundle mainBundle] loadNibNamed:#"FriendsPopUpView_iPhone" owner:self options:nil];
[self.view addSubview:self.friendsPopUpView];
UIButton* clickedButton = (UIButton*) sender;
CGRect sFrame = CGRectMake(clickedButton.frame.origin.x-100, clickedButton.frame.origin.y,
self.friendsPopUpView.frame.size.width,
self.friendsPopUpView.frame.size.height);
self.friendsPopUpView.frame = sFrame;
}
}
Does the popup view nib contain hooked up outlets to the MainWindowViewController class (like self.friendsPopUpView)? It must in order for anything to work.
You can't set the delegate and datasource before the table view exists. It doesn't exist when the MainWindowViewController viewDidLoad fires. To setup the delegate and datasource in code, do it after the nib is loaded, once the table exists.
If you setup the other outlets (like the friendsPopUp and the friendsTableView) as nib outlets (connected to the "file's owner" which you would have set as MainWindowViewController) then you can set the delegate and datasource the same way, no code required. Otherwise, do it in code after you load the nib...
- (IBAction)on_friends:(id)sender {
if (self.friendsPopUpView == nil) {
[[NSBundle mainBundle] loadNibNamed:#"FriendsPopUpView_iPhone" owner:self options:nil];
// assuming you have a friendsPopUpView outlet setup in the nib
// also assuming you have a friendsTableView outlet setup in the nib, both of these connected
// now this will work
self.friendsTableView.delegate = self;
self.friendsTableView.dataSource = self;
In my app I habe a view controller that calls several views. All these views are UIViews. That works fine, but not in every case. One of the views that are called has some labels, textfields and two UITextViews. Everything is shown correctly but the UITextViews. The view is called in that way:
[[self view] addSubview:tasteView];
//tasteView = [[TasteView alloc] init];
[self setCurrentView:tasteView];
I call the init method of the view to display the UITextViews:
EDIT: After a comment of Phillip Mills this was slightly changed! Init isn't called anymore.
- (id)init
{
if (self)
{
[tv1 setNeedsDisplay];
CGRect frame = tv1.frame;
frame.size.height += 1;
tv1.frame = frame;
}
return self;
}
As I saw that setNeedsDisplay had no effect, I changed the size of the corresponsing frame to force a redraw. Unfortunately that had no effect, too.
Btw, the view is initially loaded in the viewDidLoad of the view controller:
- (void)viewDidLoad
{
[super viewDidLoad];
[self setCurrentView:placeholder];
[self configureView];
wineryView = [self loadWineryView];
wineView = [self loadWineView];
tasteView = [self loadTasteView];
}
A method for loading the views looks like this:
- (UIView *) loadTasteView
{
NSArray *nibViews = [[NSBundle mainBundle] loadNibNamed:#"TasteView" owner:self options:nil];
UIView *tView;
for (id view in nibViews)
{
if ([view isKindOfClass:[TasteView class]])
{
tView = (TasteView*) view;
}
}
return tView;
}
I do not know why those UITextViews are not shown. Did I forget something? To show really everything, here are the connections that I made in InterfaceBuilder:
Does anyone know what I did wrong and can help me?
I think your initial code should be like this :
tasteView = [[TasteView alloc] init];
[[self view] addSubview:tasteView];
[self setCurrentView:tasteView];
addSubView after it is allocated
Hope it helps you
If you are creating the view in code (your first sample), alloc and init the view before trying to add it as a subview.
If you're loading it from another nib (last code section), you still need to add it to the view hierarchy.
I have a UINavigationController/UITableView and I want to present a UIView over top of it when the table is empty to give the user a prompt on how to add items to the table.
I've never make a UIView (as opposed to a UIViewController before) so I'll step through what I did to make it:
Make a new UIView Class - MakeSentenceHelperView
Make a nib called MakeSentenceHelperView.xib
Set File's owner to MakeSentenceHelperView
Load the nib in the MakeSentenceHelperView:
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
NSLog(#"makesentencehelperview init");
[[NSBundle mainBundle] loadNibNamed:#"MakeSentenceHelperView" owner:self options:nil];
}
return self;
}
and present the MakeSentenceHelperView in the UITableViewController:
//present the placeholder view for sentences
MakeSentenceHelperView *makeSentenceHelperView = [[MakeSentenceHelperView alloc] init];
NSLog(#"present placeholder: self.navigationcontroller.view: %#", self.navigationController.view);
//Something like this:
[self.navigationController.view addSubview:makeSentenceHelperView];
[self.navigationController.view bringSubviewToFront:makeSentenceHelperView];
The class loads and logs ok, but nothing appears in front of the UITableView - where have I gone wrong?
UPDATE: if I add [self.tableView setHidden:YES]; then the tableview disappears and the space is black and empty. I'm assuming this means I'm setting up the View wrong, somewhere.
You can use https://github.com/ecstasy2/toast-notifications-ios/ for showing Toast view liek Android. Check array size and if table view is not showing then called this one and show any custom method.
Thanks to #Aadhira for their link which led me to the problem.
I needed to add awakeFromNib
I was missing [self addSubview:self.view]; at the end of initWithFrame.
Ok, here's another question.
I am creating a UIView called ProgressView that is a semi-transparent view with an activity indicator and a progress bar.
I want to be able to use this view throughout different view controllers in my app, when required.
I know of 3 different ways of doing this (but I am only interested in one):
1) Create the entire view programatically, instantiate and configure as required. No worries I get that one.
2) Create the UIView in interface builder, add the required objects and load it using a method like the below. Problem with this is that we are basically guessing that the view is the objectAtIndex:0 because nowhere in the documentation I found a reference to the order of the elements returned from the [[NSBundle mainBundle] loadNibName: function.
NSArray *nibContents = [[NSBundle mainBundle] loadNibNamed:#"yournib"
owner:self
options:nil];
UIView *myView = [nibContents objectAtIndex:0];
myView.frame = CGRectMake(0,0,300,400); //or whatever coordinates you need
[scrollview addSubview:myView];
3) Subclass UIViewController and let it manage the view as per normal. In this case I would never be actually pushing the view controller onto the stack, but only its main view:
ProgressViewController *vc = [[ProgressViewController alloc] initWithNibName:#"ProgressView" bundle:nil];
[vc.view setCenter:CGPointMake(self.view.center.x, self.view.center.y)];
[self.view addSubview:vc.view];
[vc release];
As far as I can tell, #3 is the the correct way of doing this (apart from programatically) but I am not entirely sure if it is safe to release the ProgressView's view controller whilst another controller's view is retaining its main view (gut feel says it is going to leak?)?
What do I do in terms of memory management in this case, where and when should I release the ProgressView's view controller?
Thanks in advance for your thoughts.
Cheers,
Rog
I think that your solution #3 adds unnecessary complexity by introducing a UIViewController instance just as a container for your ProgressView so that you can setup nib bindings. While I do think that it is nice to be able to work with an IBOutlet bound property rather than iterating through the nib contents you can do so without introducing a UIViewController whose behavior you neither need nor want. This should avoid your confusion around how and when to release the view controller and what, if any, side effects it might have on the responder chain or other behaviors of the loaded view.
Instead please reconsider using NSBundle and taking advantage of the power of that owner argument.
#interface ProgressViewContainer : NSObject {
}
#property (nonatomic, retain) IBOutlet ProgressView *progressView;
#end
#implementation ProgressViewContainer
#synthesize progressView = progressView;
- (void) dealloc {
[progressView release];
[super dealloc];
}
#end
#interface ProgressView : UIView {
}
+ (ProgressView *) newProgressView;
#end
#implementation ProgressView
+ (ProgressView *) newProgressView {
ProgressViewContainer *container = [[ProgressViewContainer alloc] init];
[[NSBundle mainBundle] loadNibNamed:#"ProgressView" owner:container options:nil];
ProgressView *progressView = [container.progressView retain];
[container release];
return progressView;
}
#end
Create a nib named "ProgressView" containing a ProgressView and set it's File's Owner class to ProgressViewContainer. Now you can create ProgressViews loaded from your nib.
ProgressView *progressView = [ProgressView newProgressView];
[scrollView addSubview:progressView];
[progressView release];
If you have multiple configurations of your progress view then maybe you'll want to implement a -initWithNibNamed: method on ProgressView instead of +newProgressView so you can specify which nib to use to create each ProgressView instance.
I vote for option #2. The return value from -[NSBundle loadNibNamed] is an array of the top-level objects. So as long as you have just one top level object in your nib, then the index 0 will be correct. The other views are subviews and not top level objects.
Another option of course is to do something like create a superclass for all of your view controllers that includes an outlet called something like 'progressView' and then connect your view to that outlet on file's owner in the nib. Seems like overkill for this, though.
I also prefer alternative #2. If the "0" is bothering you, you could:
Create a subclass of UIView called ProgressView
Create a nib-file called ProgressView.xib describing your progress view.
Select the topmost view in your nib, and set its Class to ProgressView in interface builder
then do
NSArray *nibContents = [[NSBundle mainBundle] loadNibNamed:#"ProgressView" owner:self options:nil];
ProgressView *progressView = nil;
for (UIView *view in nibContents) {
if ([view isKindOfClass:[ProgressView class]]) {
progressView = (ProgressView *) view;
break;
}
}
if (progressView != nil) {
//Use progressView here
}
I ended up adding a category to UIView for this:
#import "UIViewNibLoading.h"
#implementation UIView (UIViewNibLoading)
+ (id) loadNibNamed:(NSString *) nibName {
return [UIView loadNibNamed:nibName fromBundle:[NSBundle mainBundle] retainingObjectWithTag:1];
}
+ (id) loadNibNamed:(NSString *) nibName fromBundle:(NSBundle *) bundle retainingObjectWithTag:(NSUInteger) tag {
NSArray * nib = [bundle loadNibNamed:nibName owner:nil options:nil];
if(!nib) return nil;
UIView * target = nil;
for(UIView * view in nib) {
if(view.tag == tag) {
target = [view retain];
break;
}
}
if(target && [target respondsToSelector:#selector(viewDidLoad)]) {
[target performSelector:#selector(viewDidLoad)];
}
return [target autorelease];
}
#end
explanation here: http://gngrwzrd.com/blog-view-controller-less-view-loading-ios-mac.html