I have a subclassed UIView which I can get to scroll but not to zoom. I'm using autolayout so wondered if anything had changed in IOS6. In particular when are the scrollViewWillBeginZooming and scrollViewDidEndZooming methods implemented. My code looks like
- (void)viewDidLoad
{
[super viewDidLoad];
self.ringSet2 = [[RingView alloc] initWithFrame:CGRectMake(0, 0, 800, 800)];
[self.ringSet2 setDefaults];
/// ... more setup for other views but only ringSet2 is scrolled.
self.scrollview1.delegate=self;
self.scrollview1.scrollEnabled=YES;
self.scrollview1.contentSize=self.ringSet2.bounds.size
self.scrollview1.minimumZoomScale=0.2;
self.scrollview1.maximumZoomScale=5.0;
self.ringSet2.userInteractionEnabled=YES;
// ... needed elsewhere so other views can pick up their dimensionts
[self.view setNeedsLayout];
[self.view layoutIfNeeded];
/// ... code for additional views
[self.scrollview1 zoomToRect:CGRectMake(0, 0, 200, 200) animated:YES];
[self.scrollview1 addSubview:self.ringSet2];
}
with
-(UIView*)viewForZoomingInScrollView:(UIScrollView *)scrollView{
return self.ringSet2;
}
and scrollViewWillBeginZooming etc. implemented just to trace what is happening. Interesting,
viewForZoomingInScrollView appears to get called only once, as is scrollViewDidEndZooming with a scale value just under one but scrollViewWillBeginZooming is never called. The property ringSet2 is defined
#property (strong, nonatomic) IBOutlet RingView *ringSet2;
as the view does not appear if it's defined as weak.
Apologises for this. The answer is really stupid. I'm currently developing under IOS 6.0 on the simulator whilst my phone is still on IOS 5. The simulator allows you to simulate pinches but only centred on the centre of the screen. The scrollview however only receives this signal if it too is in the centre which was not the case in my initial build. Moving the scroll view's rectangle into the centre fixed the problem. You would not make this mistake testing on a real device. The code is thus OK. Lost 3 or 4 evenings pulling my hair over this one.
Related
I'm in a dilemma which method to use for setting frames of custom UIViews with many subviews in it and still have animations and automatically adjust to rotations. What I usually do when I create a new viewcontroller is alloc my custom view in loadView or viewDidLoad, e.g:
-(void)viewDidLoad
{
[super viewDidLoad];
detailView = [[DetailView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
detailView.autoresizingMask = UIViewAutoresizingFlexibleHeight|UIViewAutoresizingFlexibleWidth;
self.view = detailView;
}
Normally this width & height is not correct for an iPhone5-screen (the actual view-frame is not set until viewWillAppear) but because of the autoresizingmask it all works out.
Then in the initWithFrame of the custom UIView DetailView, I alloc all subviews with CGRectZero, e.g:
-(id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
label = [[UILabel alloc] initWithFrame:CGRectZero];
[self addSubview:label];
}
}
Then I override layoutsubviews to set all frames of all subviews. This works perfectly for any screen size and any orientation, e.g:
-(void)layoutSubviews
{
[super layoutSubviews];
label.frame = CGRectMake(0, 0, self.frame.size.width, self.frame.size.height);
}
However, I just found out that layoutSubviews is not so great when you use animations, because when you use animations in an animationblock, layoutsubviews is called in the middle of the animation and it completely breaks the animation, e.g:
-(void)animateLabel
{
[UIView animateWithDuration:0.4f animations:^
{
label.transform = CGAffineTransformMakeTranslation(100, 100);
}];
}
I believe there are ugly workarounds this by using flags for each animation and in layoutsubviews use those flags to set the correct start or endframe of the animated block but I don't think I should have to create a flag for each animation I want to do.
So my problem is now: how am I supposed to have a custom UIView WITH animations that also automatically adjusts itself to rotations?
The only solution I can come up with right now (that I don't like):
Don't use layoutSubviews but use the setFrame/setBounds method of the custom UIView to set the frames of all subviews. Then check in the viewController every time a rotation occurs and then use the setFrame/setBounds method of the custom UIView to change all frames of all subviews. I don't like this solution because the rotation methods are different in iOS5 and iOS6 and I don't want to have to do this in every UIViewController with it's own custom UIView.
Any suggestions?
I have recently started overriding viewDidLayoutSubviews (many times instead of viewWillAppear) in my UIViewControllers.
Yes viewDidLayoutSubviews is called on rotations. (from comment)
The method fires after all the internal layouts have already been completed so all finalized frames should be setup, but still give you the time you need to make adjustments before the the view is visible and shouldn't have any issues with animations because you are not already inside an animation block.
viewcontroller.m
- (void)viewDidLayoutSubViews {
// At this point I know if an animation is appropriate or not.
if (self.shouldRunAnimation)
[self.fuView runPrettyAnimations];
}
fuView.m
- (void)runPrettyAnimations {
// My animation blocks for whatever layout I'd like.
}
- (void)layoutSubviews {
// My animations are not here, but non animated layout changes are.
// - Here we have no idea if our view is visible to the user or may appear/disappear
// partway through an animation.
// - This also might get called far more than we intend since it gets called on
// any frame updates.
}
I know Auto Layout can be used to make the sizes and position consistent when the orientation changes. Is it possible to completely change the layout when the orientation changes?
For example, please look at the below wire-frame of a simple login screen in portrait mode.
Now if I rotate the device, I want to completely re-position the controls.
Can this kind of a thing be done by using Auto Layout? If not, how should I go about this?
Thank you.
In your case it can be achieved by two methods, instead of reframing every component you can group the controls like the following..
Parent View -> User Info View -> All User Info controls.. By doing this you will have to just reframe the User Info View not all the controls..
Then only Logo and Company name left to reframe.. Total 3 controls to reframe if you group your controls..
Create two views one for Portrait and other for Landscape mode, and just add and remove on rotations.. this is the fastest way as you won't have to readjust the frame by tedious code.
Hope above helps.
you cant set frames differently : -
-(void)setFramesForPotrait
{
// set frames here
}
-(void)setFramesForLandscaeMode
{
// set frames here
}
-(bool)shouldAutorotate.....
{
return Yes
}
-()willAutoRotate......
{
if(orientation = uiinterfaceOrientationPotrait)
{
[self setFramesForPotrait];
}
else
{
[self setFramesForLandscape];
}
I've had the same problem and here's what I've found so far.
The approach I'm using is having two sets (potentially multiple) sets of constraints for different screen sizes or orientations. So I just design for portrait and connect all constraints that are portrait-specific to a IBOutletCollection:
Then switch the VC orientation in InterfaceBuilder to Landscape and add the required constraints for this orientation and connect to the corresponding outlet:
Then in code I'm removing or adding the constraints as required:
- (void)viewWillLayoutSubviews
{
[super viewWillLayoutSubviews];
[self updateViewConstraints];
}
- (void)updateViewConstraints
{
[super updateViewConstraints];
if (self.view.frame.size.width >= kSize * 2 + kPadding * 3)
{
[self.view removeConstraints:self.constraintsForPortrait];
[self.view addConstraints:self.constraintsForLandscape];
} else
{
[self.view removeConstraints:self.constraintsForLandscape];
[self.view addConstraints:self.constraintsForPortrait];
}
}
You can achieve similar results with this code, basing your adding/removing of constraints on orientation instead of frame size:
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
[super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
[self updateViewConstraints];
}
But keeping in mind that Apple is rumored to release devices with different screen aspect ratios and obsoleting the UIInterfaceOrientation and rotation events, it might be worth preparing in advance.
Will see what future brings.
The downside of the approach is that that Xcode will be complaining about the design being ambiguous, which it actually is, since Xcode doesn't know we'll be removing the ambiguity at runtime. To tackle that you could set the priority of the constraints to be optional for all other orientations that you're not designing for at this particular moment (e.g. if you're designing for Portrait then optionalize the constraints for Landscape).
When using autolayout you can override updateViewConstraints to modify the layout on orientation change:
- (void)updateViewConstraints{
[super updateViewConstraints];
//portrait
if(UIInterfaceOrientationIsPortrait(self.interfaceOrientation)){
}
//landscape
else{
//
}
}
It can be done with layout constraints something like I have below. This doesn't quite work, because your sketch doesn't quite work -- your landscape view is too long compared with what you really get in landscape. You would have to shorten the login text fields for everything to fit, but this should give you an idea how it's done. The buttonWidth constraint shows how to have a negative correlation between the width of the view and the width of a button -- that is, the button's width will be less in landscape than in portrait. I have several IBOutlets to constraints that I reference in the code. I can describe them for you if you're interested, but I'll just throw this out there for now:
#implementation ViewController {
IBOutlet NSLayoutConstraint *buttonWidth;
IBOutlet NSLayoutConstraint *logoTop;
IBOutlet NSLayoutConstraint *logoAlignToLabel;
IBOutlet NSLayoutConstraint *logoSpaceToLabel;
IBOutlet NSLayoutConstraint *coNameToButtonAlignment;
IBOutlet UIButton *b;
IBOutlet UIImageView *logo;
IBOutlet UILabel *coName;
NSLayoutConstraint *con2;
NSArray *cons1;
}
- (void)viewDidLoad {
[super viewDidLoad];
[b removeConstraint:buttonWidth];
buttonWidth = [NSLayoutConstraint constraintWithItem:b attribute:NSLayoutAttributeWidth relatedBy:0 toItem:self.view attribute:NSLayoutAttributeWidth multiplier:-.2193 constant:350];
[self.view addConstraint:buttonWidth];
[self.view layoutSubviews];
}
- (void)updateViewConstraints{
[super updateViewConstraints];
if(UIInterfaceOrientationIsPortrait(self.interfaceOrientation)){
if (con2 != nil) {
[self.view removeConstraints:cons1];
[self.view removeConstraint:con2];
[self.view addConstraints:#[logoAlignToLabel,logoSpaceToLabel,logoTop,coNameToButtonAlignment]];
}
}else{
NSLog(#"Landscape");
[self.view removeConstraints:#[logoAlignToLabel,logoSpaceToLabel,logoTop,coNameToButtonAlignment]];
cons1 = [NSLayoutConstraint constraintsWithVisualFormat:#"|-8-[logo]-4-[coName]" options:NSLayoutFormatAlignAllCenterY metrics:0 views:#{#"logo":logo, #"coName":coName}];
con2 = [NSLayoutConstraint constraintWithItem:logo attribute:NSLayoutAttributeCenterY relatedBy:0 toItem:self.view attribute:NSLayoutAttributeCenterY multiplier:1 constant:0];
[self.view addConstraints:cons1];
[self.view addConstraint:con2];
}
}
On rotation the logo and company name label have their constraints removed, and new ones put in place. The new constraint for the button, that I put on in viewDidLoad, takes care of rotation automatically, so I don't have to adjust it at all during the rotation.
I want to simply have a loop so that an object continuously moves across the screen at the bottom. Here is my code it should be pretty easy to understand.
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
[self performSelector:#selector(spawnRocket) withObject:self afterDelay:2]; //delay before the object moves
}
-(void)spawnRocket{
UIImageView *rocket=[[UIImageView alloc]initWithFrame:CGRectMake(-25, 528, 25, 40)]; //places imageview right off screen to the bottom left
rocket.backgroundColor=[UIColor grayColor];
[UIView animateWithDuration:5 animations:^(){rocket.frame=CGRectMake(345, 528, 25, 40);} completion:^(BOOL finished){if (finished)[self spawnRocket];}]; //this should hopefully make it so the object loops when it gets at the end of the screen
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
After doing all this i click run and all i see is a white screen on my iphone 6.0 simulator
ps. im running xcode 4.5.1
A few things:
UIImageView *rocket=[[UIImageView alloc]initWithFrame:...
You're not assigning an image to the image view, the best way to do this is to use:
UIImage* image = [UIImage imageNamed:#"image.png"];
UIImageView *rocket = [[UIImageView alloc] initWithImage:image];
rocket.frame = CGRectMake(-25, 528, 25, 40);
(The root cause of your problem) You are not adding your UIImageView to your main view, hence it's not being displayed. In spawnRocket, you should do:
[self.view addSubview:rocket];
Note: Because you want this to be done in a loop, you're gonna have to make sure your memory management is in order.
I don't know whether you still want the rocket on screen after it's finished moving, but if not, remember to keep a reference to the UIImageView and removeFromSuperview when you're done (to prevent memory leaks).
Calling spawnRocket in viewDidLoad is probably not the best idea, it may not reach the screen yet when spawnRocket is called. Try calling it in viewWillAppear or viewDidAppear (whatever is best in your case)
[self performSelector:#selector(spawnRocket) withObject:self afterDelay:2];
You don't need to provide self within withObject:, you're not accepting any parameters within spawnRocket
You don't add the UIImageView to any parent view. It will just live in memory, but not be displayed. Add it to your view controller's view after creating it:
[self.view addSubview:rocket];
I have seen this question being addressed several times here at SO, e.g Problem with UIScrollView Content Offset, but I´m still not able to solve it.
My iphone app is basically a tab bar controller with navigation bar. I have a tableview controller made programmatically and a DetailViewController that slides in when I tap a cell in my tableview controller.
The DetailViewController is made in IB and has the following hierarchy:
top view => UIScrollView => UIView => UIImage and a UITextField.
My goal is to be able to scroll the image and text field and this works well. The problem is that my UIScrollView always gets positioned at the bottom instead at the top.
After recommendations her at SO, I have made my UIScrollView same size as the top view and instead made the UIView with the max height (1500) of my variable contents.
In ViewDidLoad I set the contentSize for the UIScrollView (as this is not accessible from IB):
- (void)viewDidLoad {
[scrollView setContentSize:CGSizeMake(320, 1500)];
[scrollView setContentOffset:CGPointMake(0, 0) animated:YES];
NSLog(#"viewDidLoad: contentOffset y: %f",[scrollView contentOffset].y);
}
Specifically setting the contentOffset, I would expect my scrollView to always end up at the top. Instead it always go to the bottom. It looks to me that there is some autoscrolling beyond my control taking place after this method.
My read back of the contentOffset looks OK. It looks to me that there may be some timing related issues as the scrolling result may vary whether animation is YES or NO.
A ugly workaround I have found is by using this delegate method:
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrView {
NSLog(#"Prog. scrolling ended");
[scrollView setContentOffset:CGPointMake(0, 0) animated:YES];
}
This brings my scrollview to top, but makes it bounce down and up like a yo-yo
Another clue might be that although my instance variables for the IBOutlet are set before I push the view controller, the first time comes up with empty image and textfield:
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if (!detailViewController) {
detailViewController = [[DayDetailViewController alloc] init];
}
// Pass dictionary for the selected event to next controller
NSDictionary *dict = [eventsDay objectAtIndex:[indexPath row]];
// This method sets values for the image and textfield outlets
[detailViewController setEventDictionary:dict];
// Push it onto the top of the navigation controller´s stack.
[[self navigationController] pushViewController:detailViewController animated:NO];
}
If I set animation to YES, and switch the order of the IBOutlet setting and pushViewController, I can avoid the emptiness upon initialization. Why?
Any help with these matters are highly appreciated, as this is really driving me nuts!
Inspired of Ponchotg´s description of a programmatically approach, I decided to skip interface builder. The result was in some way disappointing: The same problem, with the scrollview ending up in unpredictable positions (mostly at bottom), persisted.
However, I noticed that the scroll offset error was much smaller. I think this is related to the now dynamic (and generally smaller) value of ContentOffset. After some blind experimenting I ended up setting
[textView setScrollEnabled:YES];
This was previously set to NO, as the UITextView is placed inside the scrollview, which should take care of the scrolling. (In my initial question, I have erroneously said it was a UITextField, that was wrong)
With this change my problem disappeared, I was simply not able to get into the situation with scrollview appearing at bottom anymore in the simulator! (At my 3G device I have seen a slight offset appear very seldom, but this is easily fixed with scrollViewDidEndScrollingAnimation delegate described previously ).
I consider this as solved now, but would appreciate if anyone understand why this little detail messes up things?
OK! i have a question before i can give a correct answer.
Why are you using a UIView inside the Scrollview?
You can always only put your UIImageView and UITextField inside the UIScrollView without the UIView
and set the contentSize dynamically depending on the size of the text.
to give you an example how i do it:
int contSize = 0;
imageView.frame = CGRectMake(10, 0, 300, 190);
imageView.image = [UIImage imageNamed:#"yourimage"];
contSize = 190 //or add extra space if you dont want your image and your text to be so close
[textField setScrollEnabled:NO];
textField.font = [UIFont systemFontOfSize:15];
textField.textColor = [UIColor grayColor];
textField.text = #"YOUR TEXT";
[textField setEditable:NO];
textField.frame = CGRectMake(5, contSize, 310, 34);
CGRect frameText = textField.frame;
frameText.size.height = textField.contentSize.height;
textField.frame = frameText;
contSize += (textField.contentSize.height);
[scrollView setScrollEnabled:YES];
[scrolView setContentSize:CGSizeMake(320, contSize)];
In the above example I first create an int to keep track of the ysize of my view then Give settings and the image to my UIImageView and add that number to my int then i give settings and text to my UITextField and then i calculate the size of my text depending on how long is my text and the size of my font, then add that to my int and finally assign the contentSize of my ScrollView to match my int.
That way the size of your scrollview will always match your view, and the scrollView will always be at top.
But if you don't want to do all this, you can allways just:
[scrollView setContentOffset:CGPointMake(0, 0) animated:NO];
at the end of the code where you set your image and your text, and the NOto avoid the bouncing.
Hope this helps.
I'm using a UIWebView with text in it. When the iPhone is rotated to landscape, text doesn't fill the now wider UIWebView width. I'm using P (paragraph) tags, which should not affect content filling landscape's width. The line breaks that are visible in portrait remain the same in landscape. In Interface Builder, I haven't changed anything. In the IB Inspector, Web View Size has all solid bars under AutoSizing, which means it should fill to the landscape width right?
Here is a tweak though not a good thing to do, and something should be handled by apple itself
As you've noticed that things workfine when WebView is initialized in portrait and then you turn it to landscape. So.. what you can do is always initialize your webview with portrait bounds, add a selector which calls back after 2~3 seconds and sets the frame of webView according to your requirement.
Now as the contents started loading when the frame size of your webview were according to portrait (say 320,460) so converting your webview to landscape will automatically adjust your web view if you have this line in your code
[webViewObjet_ setAutoresizingMask:UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight];
Below is the snippet of code
- (id) initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame])
{
webViewObjet_ = [[UIWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 460)];
.....
}
}
- (void) webViewDidStartLoad:(UIWebView *)webView
{
.....
[self performSelector:#selector(chuss) withObject:nil afterDelay:3];
// call the function chuss after 3 second
}
- (void) chuss
{
webViewObjet_.frame = CGRectMake(0, 0, self.frame.size.width, self.frame.size.height);
[webViewObjet setAutoresizingMask:UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight];
}
Now tried around with the same problem, finally did it after looking detailed at "WhichWayIsUp"-Sample from Apple.
To keep it short:
1) Disable in the View Inspector the |--| and <-->
2) `switch the View Mode from the Webview to "Aspect Fill"
Et voila ;)
Keep the vibes,
Maniac
I have the same problem. Reloading does not work, the only thing that seems to help a bit is to add the following line to the code:
self.view.autoresizingMask =
UIViewAutoresizingFlexibleWidth;
(I place it in the willAnimateFirstHalfOfRotationToInterfaceOrientation function)
It still keeps a small white margin at the right, but its far better than the default. Note: you should apply this on the self.view and not on the UIWebView instance, that won't work.
Waiting for a solution from Apple..
Pieter
This will sound strange, but it works:
If the UIWebView is inside a UINavigationController, it will all work just fine. I had the same problem, so I just wrapped it up in a UINavigationController and the problem was gone.
For some reason, UINavigationController makes rotations work like a charm.