I have a button I added manually on the storyboard. I would like when the screen loads, to this button sets its center with the coordination I give to it by
-(void)viewWillLayoutSubviews{
NSLog(#"Will Layout");
CGPoint buttonPoint;
buttonPoint.y = self.view.frame.size.height - 50;
buttonPoint.x = self.view.frame.size.width /2;
[menuButton setCenter:buttonPoint];
}
But nothing changes, the button stays still on the place I first dragged it.
What am I doing wrong?
It's very simple for both the modern AutoLayout based and old legacy Springs&Struts based layout management approach.
AUTOLAYOUT
You simply set up two constraints that will keep the button in the horizontal middle and in the vertical middle minus 50 points up.
SPRINGS & STRUTS
You will make sure the button is set to be in the center by setting the autoresizing mask properly in the Size inspector. Then in code, in view controller's viewDidLoad method, you will fix the 50 points offset.
(Some might suggest viewWillLayoutSubviews, but let's not complicate it for now.)
- (void)viewDidLoad
{
[super viewDidLoad];
self.button.frame = CGRectMake(self.button.frame.origin.x,
self.button.frame.origin.y - 50,
self.button.frame.size.width,
self.button.frame.size.height);
/// other code....
}
Related
Im learning iOS development right now, XCode doesnt allow me to edit width and height of buttons which are in stack view:
In the Storyboard I create a new button of size 30 x 30 with a custom image and then make more 5 copies of that button. Then I embed them after selecting all of them in a Stack View. Now a disaster happens, the buttons are resized to god knows what size and they appear huge and when I try to go to size inspector to resize those buttons I see that "Width" and "Height" fields are disabled.
I tried few suggestions on stackoverflow and selected the stack view and change the distribution of stack view to "Fill Equally" but still the buttons size is being changed. I dont want this to happen. I want a fixed size buttons in a horizontal stack view and putting them in stack view should not change the size or shape of buttons like this. Can anyone please tell me how do I fix this problem?
Please help.
Sometime Interface Builder is not easy to handle because it is a running layout system at design-time / IB_DESIGNABLE. You make changes, IB gets triggered to 'think', changes parameters, layouts again, you see it does not fit and you change again.
It can be easier to fix UIStackView's constrains to your outer layout before dropping content that will be arranged by taking intrinsicContentSize of the subviews into its calculation. Even worse, if the stackview does not have complete constrains already and you drop something in as being arranged, it will take the default size as intrinsicContentSize of the dropped view and change the stackview spacing as it should. This is no surprise but it can be frustrating as convenience is disturbing your workflow here.
The docs tell you should not change intrinsicContentSize because it is not meant to be animated, it will even disturb animations and layout or even break constrains. Well, you can not set intrinsicContentSize, it is read-only. As thats for good reasons they could have written that while UIView's are instanced they can have supportive variables which have to be set before laying out which allows you to make pre-calculations.
While in code this can be tricky also, you can subclass UIView to make arranged subview instances more supportive to your needs.
There is UIView's invalidateIntrinsicContentSize that triggers the layout to take changed intrinsicContentSize into the next layout cycle. You still cant set intrinsicContentSize, but thats not needed when you would have a class designed like shown below.
// IntrinsicView.h
#import UIKit
IB_DESIGNABLE
#interface IntrinsicView : UIView
-(instancetype)initWithFrame:(CGRect)rect;
#property IBInspectable CGFloat intrinsicHeight;
#property IBInspectable CGFloat intrinsicWidth;
#end
// IntrinsicView.m
#import "IntrinsicView.h"
#implementation IntrinsicView {
CGFloat _intrinsicHeight;
CGFloat _intrinsicWidth;
}
- (instancetype)initWithFrame:(CGRect)frame {
_intrinsicHeight = frame.size.height;
_intrinsicWidth = frame.size.width;
if ( !(self = [super initWithFrame:frame]) ) return nil;
// your stuff here..
return self;
}
-(CGSize)intrinsicContentSize {
return CGSizeMake(_intrinsicWidth, _intrinsicHeight);
}
-(void)prepareForInterfaceBuilder {
self.frame = CGRectMake(self.frame.origin.x, self.frame.origin.y, _intrinsicWidth,_intrinsicHeight);
}
#end
Now this gives you control of the behaviour when UIStackView will layout.
Let's look at instancing of your UIStackView.
#import "IntrinsicView.h"
- (void)viewDidLoad {
[super viewDidLoad];
UIStackView *column = [[UIStackView alloc] initWithFrame:self.view.frame];
column.spacing = 2;
column.alignment = UIStackViewAlignmentFill;
column.axis = UILayoutConstraintAxisVertical; //Up-Down
column.distribution = UIStackViewDistributionFillEqually;
CGFloat quadratur = 30.0;
for (int row=0; row<5; row++) {
IntrinsicView *supportiveView = [[IntrinsicView alloc] initWithFrame:CGRectMake(0, 0, quadratur, quadratur)];
// supportiveView stuff here..
[column addArrangedSubview:supportiveView];
}
[self.view addSubview:column];
}
Don't forget IntrinsicView's intrinsicContentSize is set before instancing is complete, so this example takes frame size at initWithFrame as intended and stores that size to be used when intrinsicContentSize is asked. Having that still needs that UIStackView is large enough to layout nicely but you forced the arranged subviews to that intrinsic size. Btw. the example is arranged up..down.
You can use the IntrinsicView in Interface Builder, just change the views inside UIStackView to the above written class. IB will automatically update the designable API and serve you propertys you can set up. This still needs the StackView to have at least width and height set and also constrains if needed. But it takes away the impression your width and height of arranged views would have any effect other than expected, because IntrinicViews height + width is inactive in IB then.
Just to show you how much this improves your possibilities in IB, see image
I have a scrollview where I have added different views (like tutorial).
What I wanted to have is slider with below design where on scroll I will see previous tut on left side and next on right side.
For this what I have added is scrollview with paging enabled and adding UILabel (for now) in for loop. After adding label in scrollview below is what I had.
To see the data on the left & right, what I did is uncheck clip subviews from storyboard.
However what I noticed is I can scroll only in scrollview area and not outside.
Any idea how can I make UILabel make scrolling outside & inside scrollview.
As of now to make it working, what I have done is added swipe gesture on view and making scrolling programmatically. However what I was looking is if I scroll outside scrollview, it should scroll scrollview little too.
Phewww...
Finally I managed to make it done..
What I did is added one more scrollview (dummyScrollView) with full screen width above main scrollview (mainScrollView) (which I am using to show label).
Now I enabled paging for the dummyScrollView too and implement below where I am scrolling my mainScrollView based on the factor calculation for the dummyScrollView
#pragma mark - UIScrollView Delegate
- (void) scrollViewDidScroll:(UIScrollView *)sender
{
float myFactor = 0;
// 44232 is tag for new scrollview
if (sender.tag==44232) {
myFactor = mainScrollView.frame.size.width/duplicateSV.frame.size.width;
myFactor = duplicateSV.contentOffset.x*myFac;
CGRect mCC = CGRectMake(myFactor, 0, mainScrollView.frame.size.width, mainScrollView.frame.size.height);
[mainScrollView scrollRectToVisible:mCC animated:NO]; // NO is very important... YES will not work
}
// 44231 is main scrollview tag where I won't be doing anything...
if (sender.tag==44231) {
}
}
I need to implement the ability to display a label perfectly centered on screen with nothing else visible, but this needs to be done in a UITableView. The setup is a UISpiltViewController that has a UITableViewController for the detail view controller, and when no item is selected on the left I want to display a message stating that on the right, and when the user selects an item that label should instantly disappear and reveal the table. (Just like the Mail app.)
I already have this set up and it's working ok, but for some reason it's not always staying centered on screen, and it isn't a very good solution - there are some minor oddities for example you can partially see the top of the table while the rotation is occurring. I am just creating a UILabel, setting its frame to fill the visible area, then setting the table's tableHeaderView to that label, and finally disabling the ability to scroll the table. And upon rotation, the frame has to be updated to fill the visible area again. That's where the oddities occur, because it's not updating until after the rotation completes.
My question is, what is a better approach to implement this behavior? Is there some way I can prevent having to update the frame after rotation, would it be possible to use Auto Layout for the tableHeaderView?
//Setting the tableHeaderView
self.tableView.tableHeaderView = self.label;
//Creating the UILabel
- (UILabel *)label {
_label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, self.tableView.bounds.size.width,
self.tableView.bounds.size.height
- self.navigationController.navigationBar.frame.size.height
- self.tabBarController.tabBar.frame.size.height
- MIN([UIApplication sharedApplication].statusBarFrame.size.height,
[UIApplication sharedApplication].statusBarFrame.size.width))];
_label.text = #"Nothing Selected";
_label.textAlignment = NSTextAlignmentCenter;
_label.backgroundColor = self.tableView.backgroundColor;
return _label;
}
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
if (_label) {
_label.frame = CGRectMake(0, 0, self.tableView.bounds.size.width,
self.tableView.bounds.size.height
- self.navigationController.navigationBar.frame.size.height
- self.tabBarController.tabBar.frame.size.height
- MIN([UIApplication sharedApplication].statusBarFrame.size.height,
[UIApplication sharedApplication].statusBarFrame.size.width));
self.tableView.tableHeaderView = nil;
self.tableView.tableHeaderView = _label;
}
}
Is your detail controller a UITableViewController? If so, that makes things harder since any subviews you add (your label) become part of the table. It would be easier if you use a UIViewController, and alternately hide the label or table view when you need to. The label can be any size, and use centerX and centerY constraints to keep it centered. If you do it that way, you won't have to do anything on rotation.
You don't have to set the label as the headerView of your UITableView.
You can simply add the label to self.view. Even if you are running inside a UITableViewController, each UIViewController always has a view property.
Add the label to self.view and toggle the visibility of the label and the tableView as needed.This is much easier, than trying to fiddle the label in the tableview - hierarchy ;)
EDIT
As pointed out by #rdelmar, the UITableViewController indeed does not have a separate view which contains the tableview, but rather uses the tableview as it's view directly. -.-
Sorry. I did assume the two views were separated.
All I can say is follow #rdelmar s advice, and stop using UITableViewController. It forces you to do things the default way, and if you want to customized it you WILL have a bad time :(
EDIT 2
Ok so you have 2 options:
1) Do the following:
[_label setAutoResizingMask:(UIViewAutoResizingFlexibleWidth | UIViewAutoResizingFlexibleHeight)];
in your label getter. That way, you don't have to care about screen rotation anymore. The label will always fit its parents bounds.
2) Use UIViewController and treat the label as a sibling of the tableView. <- Better approach but requires more refactoring from your current state.
As pointed out by Cabus, the solution is to set the autoresizingMask of the label to UIViewAutoResizingFlexibleWidth | UIViewAutoResizingFlexibleHeight. That way, the label will always fit its parent. This can be done while using the label for the tableHeaderView, and there's no longer a need for detecting orientation changes.
How can I vertically center an image inside a scrollView?
I'm using storyboards in Xcode 5. The main view is embedded inside a navigation controller, and "Adjust scroll view insets" option is enabled in main Storyboard. This main view has a scrollView which size is equal to the main view size.
The imageView is inside the scrollView and it's the same size as the scrollView. Content mode is set to AspectFit.
So, hierarchy is as follows:
- UINavigationController
- UIView
- UIScrollView
- UIImageView
The image may be landscape or portrait, and can be any size (it's loaded at runtime). This is why imageView is the same size as the scrollView.
How can I vertically center the image inside the scrollView?
EDIT:
As commented before, I have set imageView's contentMode to AspectFit because the image may be too big, so I need it resized. The problem I have is that the image is not center of the scrollView.
You can check screenshot at link and download source code at link.
It will be good to use auto layout as mentioned by #Douglas. However, if you prefer the traditional way, you can also make it work.
I'll first give you the answer and then explain it to you. You should first delete the image view from the storyboard ( I'll explain it later), and then add the viewWillAppear method.
- (void)viewDidLoad
{
[super viewDidLoad];
// 1. Add the image view programatically
UIImageView * imageView = [[UIImageView alloc]initWithImage:[UIImage imageNamed:#"portrait.jpg"]];
[_scrollView addSubview:imageView];
_imageView = imageView;
}
- (void)viewWillAppear:(BOOL)animated
{
// 2. calculate the size of the image view
CGFloat scrollViewWidth = CGRectGetWidth(_scrollView.frame);
CGFloat scrollViewHeight = CGRectGetHeight(_scrollView.frame);
CGFloat imageViewWidth = CGRectGetWidth(_imageView.frame);
CGFloat imageViewHeight = CGRectGetHeight(_imageView.frame);
CGFloat widthRatio = scrollViewWidth / imageViewWidth;
CGFloat heightRation = scrollViewHeight / imageViewHeight;
CGFloat ratio = MIN(widthRatio, heightRation);
CGRect newImageFrame = CGRectMake(0, 0, imageViewWidth * ratio, imageViewHeight * ratio);
_imageView.frame = newImageFrame;
// 3. find the position of the imageView.
CGFloat scrollViewCenterX = CGRectGetMidX(_scrollView.bounds);
CGFloat scrollViewCenterY = CGRectGetMidY(_scrollView.bounds) + _scrollView.contentInset.top / 2 ;
_imageView.center = CGPointMake(scrollViewCenterX, scrollViewCenterY);
}
Here is the explanation:
You should not put the imageView in the storyboard, otherwise the frame of the imageView will be fixed by the storyboard, and will not change with the size of the image. Even if you choose UIViewContentModeScaleAspectFill, the frame of the imageView is still not changed. It just add some white space around the image.
Now the imageView has the same size as your image. If you want it to be fully displayed, you need to calculate the frame yourself.
Pay attention to the _scrollView.contentInset.top / 2, this is why you need to put the codes in viewWillAppear instead of viewDidLoad. The _scrollView.contentInset.top is the height of the navigation bar and is calculated automatically for you before willViewAppear.
You put your image view in a scrollView, I guess you want to zoom in and out. If this is true, add self.imageView = imageView; and the bottom of viewDidLoad. Set the delegate of _scrollView to self and add the following method:
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
return _imageView;
}
I made a comment, but then took a look at your project. You are almost there. I ran through the following steps and have gotten the result you are looking for.
First, make sure you have auto layout turned ON!!!
In your storyboard click on your scroll view. You had a scroll view that was the same size as the view. You are going to put on some constraints. Down at the bottom of the story board you will see some icons.
The fourth one over looks sort of like an I-beam on its side, it is the pin button. After selecting the scroll view, click on this and it will bring up a pop up menu.
For the scroll view click on all the bars around the middle block so you pin the scroll view to the sides of the main view.
You will notice they are all red now.
Then go and click on the imageview. Once again you had it set to the size of the view. Using the pin button again, you are going to pin just the Width at 320 and the Height at 568. When you are done you are then going to use the next button over.
This is the align button. Click on that after you have selected your image view. You are going to click on Horizontal Center in Container, and Vertical Center in Container.
Next you will need to add one method to your ViewController.m file.
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
[_scrollView setContentSize:CGSizeMake(self.view.frame.size.width, self.view.frame.size.height)];
}
Start up the simulator and let her rip! You will get one warning though. It says the content size is ambiguous for the scroll view. But that's OK, because you will set it on viewDidLayoutSubviews.
Hope that helps, or helps someone out. Autolayout and scroll views are a bit tough!!
EDIT#1
if you want to then make the image view scalable, by pinch zooming you can do the following.
Make sure you made the .h file follow the UIScrollViewDelagate.
#interface ViewController : UIViewController <UIScrollViewDelegate>
This will allow the scroll view to be able to access the delegate methods of the scroll view. The method you are looking for is called..
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
return self.imageView;
}
Then in the viewDidLoad method of your .m file do the following.
_scrollView.minimumZoomScale = 0.5;
_scrollView.maximumZoomScale = 4.0;
_scrollView.delegate = self;
The underscore and the variable name is the same as self.variable. Either will work.
That should do it. Let me know if it works or if you have any other questions. ENJOY!
These are the ones u can use, the 3 modes of ImageView content display.You can do this by dynamically setting them or u can set them in storyboard too, click on the ImageView and go to properties tab-bar and choose from there, run them and select which output u want.
imageView.contentMode = UIViewContentModeScaleAspectFit;
imageView.contentMode = UIViewContentModeScaleToFill;
imageView.contentMode = UIViewContentModeScaleAspectFill;
Hope this helps
If you want to center image in an imageview use
imageView.contentMode=UIViewContentModeCenter;
image retains it's size in this this content mode. Alternatively you can use other content modes as per your requirement.
UIViewContentModeScaleToFill,
UIViewContentModeScaleAspectFit, // contents scaled to fit with fixed aspect. remainder is transparent
UIViewContentModeScaleAspectFill, // contents scaled to fill with fixed aspect. some portion of content may be clipped.
UIViewContentModeRedraw, // redraw on bounds change (calls -setNeedsDisplay)
UIViewContentModeCenter, // contents remain same size. positioned adjusted.
UIViewContentModeTop,
UIViewContentModeBottom,
UIViewContentModeLeft,
UIViewContentModeRight,
UIViewContentModeTopLeft,
UIViewContentModeTopRight,
UIViewContentModeBottomLeft,
UIViewContentModeBottomRight,
my question is:
In my app I want realize somethings like this using UIScrollView
A UIScrollView whit all his contents inside. But when I reached the top of the screen (or maybe the bottom of the screen) I want to anchor some contents like in the AppStore (iOS 6).
In this case the "Detail", "Review" and "Related" buttons stop scrolling and remains fixed at the top.
Anyone have any idea how to do?
Give your scroll view a delegate if it doesn't already have one. This would probably be your view controller.
Also give the delegate a reference to the floating view. (In the example, the floating view contains the “Dettagli”, “Recensioni”, and “Correlati” buttons, and the floating view is transparent where the triangular notch is, and the shadow is also part of the floating view.) The floating view must be a subview of the scroll view.
Save the “normal” Y coordinate of the floating view's frame in an instance variable. For example, if the delegate is a view controller, you should probably do it in viewDidLoad:
- (void)viewDidLoad {
[super viewDidLoad];
originalFloatingViewY = floatingView.frame.origin.y;
}
In the delegate's scrollViewDidScroll: method, check whether the scroll view has been scrolled below the top of the “normal” position of the floating view. If so, move the floating view to the top edge of the visible area:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
[self updateFloatingViewFrame];
}
- (void)updateFloatingViewFrame {
CGPoint contentOffset = scrollView.contentOffset;
// The floating view should be at its original position or at top of
// the visible area, whichever is lower.
CGFloat y = MAX(contentOffset.y, originalFloatingViewY);
CGRect frame = floatingView.frame;
if (y != frame.origin.y) {
frame.origin.y = y;
floatingView.frame = frame;
}
}
You could also do it by subclassing UIScrollView and overriding layoutSubviews. Watch the “Advanced ScrollView Techniques” video from WWDC 2011 for more information on this technique.
You need to handle this in the UIScrollView's delegate method( scrollViewDidScroll:) for scrolling. When the y of scrollview reaches that particular value you need to disable scrolling in this direction alone. You just need to add an if condition there to check and reset the y point while scrolling. For this you might have to keep this particular portion as a separate scrollview outside the below content and move it along with the below scroll view's upward movement.