Using xib object inside another xib - iphone

I'm designing using IB a specific button (with some labels,and imageView and other stuff).
I would like then to use that xib object (the button) in another xib, where I have 6 objects of that button.
I know I can do that programmatically using the Class Identifier, but then I have to position my lables and image view, and set the default values and everything else in code.
I'm wondering if it's possible to do the same thing using just the IB ? (of course I would still set the values in code, but I want to positions of the lables/imageView and all the rest to be set in the xib)
Thanks
Edit
So, I understand what I asked for at first is not possible.
What I'm trying now is like that :
I created a ButtonTest.xib.
Inside the Xib I have a UIView and 3 subviews (2 lables and a button).
In the inspector I set the UIView class to ButtonTest.
Inside ButtonTest.m I have 3 outlets for each of the subviews, which I connect in IB.
Next I have a ButtonTestViewController.xib.
Inside it I put one view and set it's class in the inspector to be ButtonTest.
I connect that view to a myTextView outlet inside ButtonTestViewController.m of class ButtonTest
Now, this is the code inside ButtonTestViewController.m viewDidLoad:
- (void)viewDidLoad {
[super viewDidLoad];
NSArray *subviewArray = [[NSBundle mainBundle] loadNibNamed:#"ButtonTest" owner:nil options:nil];
ButtonTest *mainView = (ButtonTest*)[subviewArray objectAtIndex:0];
self.myTextView = mainView;
}
What I hoped would happen, is that the view in ButtonTestViewController.xib would become the view I designed in ButtonTest.xib.
This just isn't happening. what happens is that the view inside ButtonTestViewController.xib stays the same.
Worth mentioning, is that if I add:
[self.view addSubview:mainView];
It does add the new view, besides the existing one.
Any idea how to do what I want ?
Eventually I would like to have 6 views in ButtonTestViewController.xib, all would look like the ButtonTest.xib template, and the lables values will be set in code.
Edit2
Ok, guys I did everything you said and it worked like a charm.
The only problem I have right now with this issue is when the view in ButtonTestViewController.xib is a little bigger then view in ButtonTest.xib.
When that happens, The lable on the button look extremely blurry.
When they are both the same size, it's all good.
# Ned - I used the exact code you posted in my InitWithCoder method, except I switched the frame sentence to this :
self.bounds = mainView.frame;
I tried playing with the content mode in IB trying to fix it, to no avail.
When I do use it like you posted, meaning :
mainView.frame = self.bounds;
It's not working at all, and both views' sizes stay the same.
Here I tried playing with the resizeMask but still didn't work.
And idea how to fix these 2 issues ?
Thanks guys!
Edit 3
I did manage to fix one of the issues, resizing the ButtonTestViewController.xib to the ButtonTest.xib view size.
This is what I did (using code to solve the blurry issue, taken from here)
self.bounds = CGRectMake(0, 0, mainView.frame.size.width, mainView.frame.size.height);
CGRect overlay2Frame = self.frame;
overlay2Frame.origin.x = round(overlay2Frame.origin.x);
overlay2Frame.origin.y = round(overlay2Frame.origin.y);
self.frame = overlay2Frame;
The other issue I still can't solve. the view in ButtonTest.xib just won't get bigger to match the other view.

You can do this, and pretty easily. The way I do this is create a UIView subclass (let's say it's called "MyView.m" and "MyView.h"), and then a nib called "MyView.xib".
First, in the nib, click File's Owner, and set the class to "MyView". This will make it so IBOutlets/Actions from your class show up in IB. Add a single top-level view (if it doesn't have one already) as your main view, and add your other custom elements (UILabels and UIImageViews) as subviews of the main UIView.
Next, add the following code so that it gets called when MyView is initialized (remember, if you initialize from a nib it'll get initialized via - (id)initWithCoder:(NSCoder *)aDecoder).
NSArray *subviewArray = [[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) owner:self options:nil];
UIView *mainView = [subviewArray objectAtIndex:0];
//Just in case the size is different (you may or may not want this)
mainView.frame = self.bounds;
[self addSubview:mainView];
What this does is loads the view hierarchy from your nib into the NSArray, and since you only had one top-level UIView, you just add that as a subview of your custom UIView.
This allows you to design UIViews (and other subclasses of UIView) in Interface Builder.
EDIT: Updated to OP's edit.
The way I've always done this is by first following my directions above. One difference is that in your ButtonTest nib you're changing the class of the UIView to ButtonTest, but I change the File Owner's class to ButtonTest. Then, inside ButtonTest.m, do the loadNibNamed stuff. Now, to add that object to ButtonTestViewController.xib, add a UIView (or UIButton, whatever ButtonTest is subclassed from) and change the class to ButtonTest.
This is a little confusing, so I'll try to break it up by file.
ButtonTestViewController.xib: Add a UIButton (of whatever ButtonTest inherits from) and change the class to ButtonTest
ButtonTest.m: Inside the "initWithCoder" method, do all of the "loadNibNamed" stuff I have above
ButtonTest.xib: Change File's Owner class to ButtonTest and link up all IBOutlets and IBActions

Some of you ask in comment if this solution doesn't create infinite loop and I don't have enough reputation to answer there, so here is my answer:
The loop exist if root view in your XIB has "Custom class" set to MyView. This causes the situation where view loaded from XIB invokes initializer of MyView again and therefore, creates infinite loop. The solution is that "Custom class" should be UIView and only File Owner's class should be set to MyView (to be able to assign IBOutlets and IBActions).

Swift 3.0
create swift file with class
class ViewXIBfileClassView: UIView {
.....
}
create Xib file with view:ViewXIBfileClassView
create wrapper view class
class ViewWrapper: UIView {
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
let view = UINib(nibName: "ViewXIBfileClassView_file", bundle: nil).instantiate(withOwner: nil, options: nil)[0] as! viewXIBfileClassView
self.addSubview(view)
view.bindEdgesToSuperview()
}
}
add extension to UIView
extension UIView {
/// Adds constraints to the superview so that this view has same size and position.
/// Note: This fails the build if the `superview` is `nil` ā€“ add it as a subview before calling this.
func bindEdgesToSuperview() {
guard let superview = superview else {
preconditionFailure("`superview` was nil ā€“ call `addSubview(view: UIView)` before calling `bindEdgesToSuperview()` to fix this.")
}
translatesAutoresizingMaskIntoConstraints = false
["H:|-0-[subview]-0-|", "V:|-0-[subview]-0-|"].forEach { visualFormat in
superview.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: visualFormat, options: .directionLeadingToTrailing, metrics: nil, views: ["subview": self]))
}
}
}
Use UIView with ViewWrapper inside all Xib files you want

If your class is a subclass of UIButton for example, you can open the Library in Interface Builder and it will be under the classes tab when you search for it and you can drag and drop.
You can also just drag/drop a UIButton and set its class in the inspector pane.
However, because you have your own .xib that might mess it up, in which case just ignore me.

Related

disable tableHeaderView (Not to be confused with section header) scrolling

Is it possible to disable the scrolling of tableHeaderView (Not to be confused with section header).Right now whenever I scroll the table, view in the tableHeaderView also gets scrolled.
What i am doing:
I have a class subclassed from UITableViewController.
In storyboard, I am using the static table view.
Table style is Grouped and I have added 8 sections having a row each.
On the top of 1st section, added a view which is the tableHeaderView.
I want to disable the scrolling of view with title "Profile" when I scroll the table.
PS:
I know this is achievable if I subclassed my class from UIViewController instead of UITableViewController.
But I don't want to UIViewController because I am using storyboard for designing static cell and If I use UIViewController instead of UITableViewController then compiler throws a warning "Static table views are only valid when embedded in UITableViewController instances"
Please let me know which is the best approach to achieve this.Is it possible to disable the scrolling of tableHeader using my current approach or do I need to use UIViewController instead.
Just use an embed segue with a parent UIViewController consisting of a header view and a container view. Embed your UITableViewController in the container view. More specific steps in this answer.
If you want everything in UITableViewController, you can insert your own subview doing something like this:
- (void)viewDidLoad
{
[super viewDidLoad];
self.header = [[UIView alloc] init];
self.header.frame = CGRectMake(0, 0, self.tableView.bounds.size.width, 44);
self.header.backgroundColor = [UIColor greenColor];
[self.tableView addSubview:self.header];
self.tableView.contentInset = UIEdgeInsetsMake(44, 0, 0, 0);
}
and then manipulate the position of the view in scrollViewDidScroll and friends:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
self.header.transform = CGAffineTransformMakeTranslation(0, self.tableView.contentOffset.y);
}
I say "and friends" because you'd need to take care of the corner cases like scrollViewDidScrollToTop:. scrollViewDidScroll gets called in every display cycle during scrolling, so doing it this way looks flawless.
Timothy Moose was spot on. Here are the necessary changes for iOS8.
MonoTouch (C#)
// create the fixed header view
headerView = new UIView() {
Frame = new RectangleF(0,0,this.View.Frame.Width,44),
AutoresizingMask = UIViewAutoresizing.FlexibleWidth,
BackgroundColor = UIColor.DarkGray
};
// make it the top most layer
headerView.Layer.ZPosition = 1.0f;
// add directly to tableview, do not use TableViewHeader
TableView.AddSubview(headerView);
// TableView will start at the bottom of the nav bar
this.EdgesForExtendedLayout = UIRectEdge.None;
// move the content down the size of the header view
TableView.ContentInset = new UIEdgeInsets(headerView.Bounds.Height,0,0,0);
.....
[Export("scrollViewDidScroll:")]
public virtual void Scrolled(UIScrollView scrollView)
{
// Keeps header fixed, this is called in the displayLink layer so it wont skip.
if(headerView!=null) headerView.Transform = CGAffineTransform.MakeTranslation(0, TableView.ContentOffset.Y);
}
[Export ("scrollViewDidScrollToTop:")]
public virtual void ScrolledToTop (UIScrollView scrollView)
{
// Keeps header fixed, this is called in the displayLink layer so it wont skip.
if(headerView!=null) headerView.Transform = CGAffineTransform.MakeTranslation(0, TableView.ContentOffset.Y);
}

UIScrollView referenced both by UIViewController's 'view' property and by an outlet

I'm about to add a UIScrollView to my iPhone project and before I implement this functionality I wanted to check if my approach is the right one or if I could be violating some best practice I'm not aware of.
The tutorials I've seen generally involve adding a UIScrollView to an existing UIView and they work from there. However, I was wondering if I could spare the UIView altogether and just add the UIScrollView as the only top-level object in my nib file.
My sample project uses Xcode's View-based Application template:
Project navigator http://img542.imageshack.us/img542/5364/projectnavigator.png
I deleted the UIView top-level object from the original MySampleViewController.xib file and replaced it by adding a UIScrollView object:
Nib placeholders http://img526.imageshack.us/img526/7709/placeholderobjects.png
Now my nib file only shows this object in the canvas:
Canvas http://img233.imageshack.us/img233/4063/scrollview.png
Then I created the link from the UIViewController's view outlet to the UIScrollView.
Now, if I wanted to programmatically manipulate the contents of the UIScrollView I can use this code:
- (void)viewDidLoad {
[super viewDidLoad];
NSArray *colors = [NSArray arrayWithObjects:[UIColor redColor], [UIColor greenColor], [UIColor blueColor], nil];
// Solution B: With the following line we avoid creating an extra outlet linking to the UIScrollView top-level object in the nib file
UIScrollView *scrollView = (UIScrollView *)self.view;
for (int i = 0; i < colors.count; i++) {
CGRect frame;
//frame.origin.x = self.scroller.frame.size.width * i; // Solution A: scroller is an IBOutlet UIScrollView *scroller;
frame.origin.x = scrollView.frame.size.width * i; // Solution B
frame.origin.y = 0;
//frame.size = self.scroller.frame.size; // Solution A
frame.size = scrollView.frame.size; // Solution B
UIView *subView = [[UIView alloc] initWithFrame:frame];
subView.backgroundColor = [colors objectAtIndex:i];
//[self.scroller addSubview:subView]; // Solution A
[self.view addSubview:subView]; // Solution B
[subView release];
}
//self.scroller.contentSize = CGSizeMake(self.scroller.frame.size.width * colors.count, self.scroller.frame.size.height); // Solution A
scrollView.contentSize = CGSizeMake(scrollView.frame.size.width * colors.count, scrollView.frame.size.height); // Solution B
}
In order to implement Solution A the scroller outlet must be linked to the nib's UIScrollView as well, and the Connections Inspector looks like this:
Connections Inspector http://img228.imageshack.us/img228/8397/connectionsj.png
Solution A requires an outlet and this means having two connections to the UIScrollView: the UIViewController's own view outlet and MySampleViewController's scroller outlet. Is it legal and/or recommended to have two outlets pointing to the same view?
Solution B only involves UIViewController's view outlet linking to the view, and using this line:
UIScrollView *scrollView = (UIScrollView *)self.view;
My questions:
Do I incur in some sort of violation of Apple's design guidelines by using one of these two solutions?
Should I stick to the UIScrollView within a UIView solution?
Is there any other way to implement this?
Thanks!
P.S. Sorry for the syntax highlight, SO didn't recognize the use of the 'objective-c' tag
No I think you are fine either way.
I would, I don't think a UIView has any significant cost, plus what if you want to add a page control? and you don't have to cast the controller's view to a UIScrollView every time you need it.
Looks like you have it under control to me.
Solution A requires an outlet and this means having two connections to the UIScrollView: the UIViewController's own view outlet and MySampleViewController's scroller outlet. Is it legal and/or recommended to have two outlets pointing to the same view?
It standard to have IBOutlets to any view defined in your .nib that you want to access directly from your view controller.
If you don't want two outlets you could give the scroll view a tag then find it like so:
UIScrollView *myScrollView = (UIScrollView *)[self.view viewWithTag:1]
Then you only have the view as an outlet, but I would just add the extra outlet. Just make sure you set them to nil in your viewDidUnload.
Also you don't have to retain the scroll view (if you are even still using retain/release). Since the scroll view is inside your view controller's view it keeps a reference so you can have your scrollview's property by assign or week if your using ARC.
Hope that helps.

Shifting UITableView down (UITableViewController)

I am just wondering whether or not it is possible to shift a UITableView down the page by, say, maybe 50 pixels. I know this would usually work if I had used a UIViewController then added a table view on top, but would I be able to do this and still keep it as UITableViewController?
I had the same problem and the answer above didn't work. This did:
[self.tableView setContentInset:UIEdgeInsetsMake(50,0,0,0)];
My solution is to override tableViewcontroller's method viewWillLayoutSubviews
- (void) viewWillLayoutSubviews
{
[super viewWillLayoutSubviews];
self.tableView.frame = CGRectMake(0,0,CGRectGetWidth(self.view.frame),300);
}
Works great and always for me with changing orientations and in all situations
A UITableView is actually a UIScrollView. This means that you can scroll the UITableView to the point you want. This is a previous link which shows you how to do this, including sample code and discussion.
Edit: In order to shift the WHOLE tableview down, just use:
float yOffset = 50.0f; // Change this how much you want!
tableview.view.frame = CGRectMake(tableview.view.frame.origin.x, tableview.view.frame.origin.y + yOffset, tableview.view.frame.size.width, tableview.view.frame.size.height);
Hope that Helps!
Since a Table View is backed by a UIScrollView you can move in around using the content Offset.
self.tableView.contentOffset = CGPointMake( x, y);
You might want to wrap in a UIView animation
If you are trying to add a UI element at the top of the table, why not just set it to the tableHeaderView instead?
UILabel *someLabel;
// configure label
self.tableView.tableHeaderView = someLabel;
If you need a view behind (or on top of) the tableview, then you'll have to subclass UIViewController instead and add a UITableView afterwards.
Another solution could be to set the table's header view (reference) but in this case, keep in mind that this view will scroll together with the table.
More information about the limitations of UITableViewController in this article: "Clean table view code".
Swift 2.2:
To shift the tableView inside a UITableViewController down:
let edgeInsets = UIEdgeInsetsMake(20, 0, 0, 0)
self.tableView.contentInset = edgeInsets
UITableViewController is actually a UIViewController, only plus is it gives you some methods to override and useful for table actions. so you can do whatever you want
check this, once you get the idea of what UITableViewController actully is you will do whatever you want
http://cocoawithlove.com/2009/03/recreating-uitableviewcontroller-to.html

why is my UIView subview not rendering within the parent? (code attached)

Why is my custom UIView not rendering within the UIView container of it's parent? So what I have is:
MainViewController - has a UIView "customViewContainer" (which doesn't take up the whole screen), and
CustomView - is a UIView with a XIB file - it is the UIView here that when it is rendered (with AspectFit) is rendering outside the bounds of the parent "customViewContainer"
The code used to setup the custom view is this extract from MainViewController:
<< cut - see Update2 below >>
So I can't see why the CustomView view is being rendered in a way that is larger in area than the parent customViewContainer? What I want is for the customview to fit into the parent "customViewContainer" entirely per the AspectFit type approach.
thanks
EDIT 1 (added clarification) - If I "clip subviews" in the parent view then it does then clip things, but what I really need to render the custom view within the parent view area (not the whole area of the screen). So I need (a) the center of the custom view to be in the center of the parent view, and (b) custom view to AspectFit into the parent view properly. Any ideas?
EDIT 2 - Update
sorry - made a copy/paste mistake with code in the original question - can't seem to edit it so I'll put a correct version below - so to clarify:
MainViewController - has a UIView "containerView" (which doesn't take up the whole screen), and CustomView - is a UIView with a XIB file - it is the UIView here that when it is rendered (with AspectFit) is rendering outside the bounds of the parent "containerView"
With the code below does this make sense now? The reason for this code is I have a custom UIView, BUT I have a XIB file associated with it, so this was the only way to get my MainController view to be able to use it. That is, have a container view in the MainController view, and then programmatically add the CustomView into the container view.
Re "So set the frame or center of the view that you're adding to be what you want it to be" - are you saying I have to programmatically/manually set the dimension of the CustomView to be what I want (in relation to the parent containerView)?
What I was hoping was there was a way using the declaritive layout setting to some how be able to say "Load the Custom View from it's XIB file, and the aspectFit this view into the self.containerView", however I'm starting to wonder if this is possible?
UPDATED CODE BELOW (made mistake in original question when I copy/pasted it in and changed variables names etc)
- (void)viewDidLoad
{
[super viewDidLoad];
// Load the custom Altimeter View into this UIControllerView's container UIView for it
NSArray *nibs = [[NSBundle mainBundle] loadNibNamed:#"Customview" owner:self options:nil];
for (NSObject *obj in nibs) {
if ( [obj isKindOfClass:[Customview class]]) {
Customview *cv = (Customview*)obj;
[self.containerView addSubview:cv];
break;
}
}
// UI Layout
self.containerView.layer.borderWidth = 2;
self.containerView.layer.borderColor = [[UIColor redColor] CGColor];
}
Check if the "Clip subviews" property of parent on the IB file is checked.
I think the equivalent code is self.view.clipsToBounds = YES.
if this is NO, subviews that draws outside will be visible as if it's drawn on the parent.

UITableViewCell custom reorder control

Is there any way to change the image of the reorder control that is displayed when the UITableView is in edit mode? I have a UIImage that Iā€™d like to display instead of the usual grey bars.
Do I have to subclass UITableViewCell to accomplish this?
I guess you're a long way past this by now, but this has come up in a new question.
See my answer here:
Change default icon for moving cells in UITableView
I recently ran across the need to change the image for the reorder control, because I subclassed UITableViewCell to provide my own custom table cell. As part of this effort, I changed the background of the cell to something other than the default color.
Everything works correctly, but when I put the UITableView into editing mode, the reorder control would appear, with a white background - instead of the background color I was using for the cell. This didn't look good, and I wanted the background to match.
During the course of various versions of iOS, the hierarchy of views in a UITableViewCell has changed. I've taken an approach that will traverse the entire set of views until it finds the UITableViewCellReorderControl private class. I believe this will work for iOS 5 and all subsequent iOS versions at the time of this answer. Please note that while the UITableViewCellReorderControl class itself is private, I am not using any private API's to find it.
First, here's the code to scan for the reorder control; I'm assuming that the text "Reorder" will be in the class name - which Apple could change in the future:
-(UIView *) findReorderView:(UIView *) view
{
UIView *reorderView = nil;
for (UIView *subview in view.subviews)
{
if ([[[subview class] description] rangeOfString:#"Reorder"].location != NSNotFound)
{
reorderView = subview;
break;
}
else
{
reorderView = [self findReorderView:subview];
if (reorderView != nil)
{
break;
}
}
}
return reorderView;
}
In your custom UITableViewCell subclass, you will override -(void) setEditing:animated: and find the reorder control here. If you try to find this control when the table is not in editing mode, the reorder control will not be in the view hierarchy for the cell:
-(void) setEditing:(BOOL)editing animated:(BOOL)animated
{
[super setEditing:editing animated:animated];
if (editing)
{
// find the reorder view here
// place the previous method either directly in your
// subclassed UITableViewCell, or in a category
// defined on UIView
UIView *reorderView = [self findReorderView:self];
if (reorderView)
{
// here, I am changing the background color to match my custom cell
// you may not want or need to do this
reorderView.backgroundColor = self.contentView.backgroundColor;
// now scan the reorder control's subviews for the reorder image
for (UIView *sv in reorderView.subviews)
{
if ([sv isKindOfClass:[UIImageView class]])
{
// and replace the image with one that you want
((UIImageView *)sv).image = [UIImage imageNamed:#"yourImage.png"];
// it may be necessary to properly size the image's frame
// for your new image - in my experience, this was necessary
// the upper left position of the UIImageView's frame
// does not seem to matter - the parent reorder control
// will center it properly for you
sv.frame = CGRectMake(0, 0, 48.0, 48.0);
}
}
}
}
}
Your mileage may vary; I hope this works for you.
Here is my Swift solution based on Rick Morgan's answer:
func adjustSize() {
// we're trying to leverage the existing reordering controls, however that means the table must be kept in editing mode,
// which shrinks the content area to less than full width to make room for editing controls
let cellBounds = bounds
let contentFrame = contentView.convert(contentView.bounds, to: self)
let leftPadding = contentFrame.minX - cellBounds.minX
let rightPadding = cellBounds.maxX - contentFrame.maxX
// adjust actual content so that it still covers the full length of the cell
contentLeadingEdge.constant = -leftPadding
// this should pull our custom reorder button in line with the system button
contentTrailingEdge.constant = -rightPadding
// make sure we can still see and interact with the content that overhangs
contentView.clipsToBounds = false
// recursive search of the view tree for a reorder control
func findReorderControl(_ view: UIView) -> UIView? {
// this is depending on a private API, retest on every new iPad OS version
if String(describing: type(of: view)).contains("Reorder") {
return view
}
for subview in view.subviews {
if let v = findReorderControl(subview) {
return v
}
}
return nil
}
// hunt down the system reorder button and make it invisible but still operable
findReorderControl(self)?.alpha = 0.05 // don't go too close to alpha 0, or it will be considered hidden
}
This worked pretty well. contentLeadingEdge and contentTrailingEdge are layout constraints I set up in Interface Builder between the contentView and the actual content. My code calls this adjustSize method from the tableView(_:, willDisplay:, forRowAt:) delegate method.
Ultimately, however, I went with Clifton's suggestion of just covering the reorder control. I added a UIImageView directly to the cell (not contentView) in awakeFromNib, positioned it, and when adjustSize is called I simply bring the image view to the front, and it covers the reorder control without having to depend on any private APIs.
I put a little work into this recently, but came up short. I tried setting my own editingAccesoryView but couldn't get the reorder control to change. Odd.
My guess is that it has something to do with the following comment in the UITableviewCell docs re: showsReorderControl:
If the value is YES , the reordering
control temporarily replaces any
accessory view.
In other words, the editingAccessoryView is being replaced by the reordering control view, which might be why we cannot override the reordering control. Hoping someone can find a workaround.
You set the cell's editingAccessoryView property to an image view containing the image you want.
As an aside, I would caution you to be careful when doing this. When you substitute a custom graphic for a system standard such as the reorder graphic, you run a serious risk of confusing the user. The standard UI grammar has told them to expect the standard graphic when reordering and they may not understand the significance of your custom graphic.
Maybe we're all overthinking this. :)
Just put a custom UIImageView over the top of the default move accessory so it covers it up. Done.