I have an application in Xcode 4.5.2 that uses storyboards. I have a table view, and I want to reposition the table view so that it is a bit lower on the screen so that I can add an image and a button above where the table view starts. I have researched this question and although I have found a couple of suggestions, none have worked for me yet. The table view is accessed via a modal segue, so there is no navigation bar.
Here is what I have already tried:
1) I added a navigation bar and set its background to my image and then tried to add the button into the nav bar. Because Xcode would not let me resize the nav bar, my image was distorted as was my button and so I abandoned this effort.
2) I added a tool bar but had similar issues as I did with the navigation bar.
3) I attempted in code to move the table view down (thinking I could add the image and button in the free space above the table view). This did not work at all, my table view origin remains at the very top of the screen. but here is the code:
//called in the view did load method of the table view controller class
- (void)repositionTableView
{
float yOffset = 75.0;
CGFloat x = self.tableView.frame.origin.x;
CGFloat y = self.tableView.frame.origin.y + yOffset;
CGFloat height = self.tableView.frame.size.height;
CGFloat width = self.tableView.frame.size.width;
CGRect newTableFrame = CGRectMake(x, y, height, width);
self.tableView.frame = newTableFrame;
}
Can anyone give me any suggestions on how to approach this problem? Thanks.
Make sure:
You created a UIViewController.
UITableView is the subview of self.view.
Autolayout is disabled.
And you can move it freely.
Related
I have a popover with TabBarController in it. In one tab there is a TableViewController with a list of names. And there is a plus button, that has a modal segue to AddCharacterVC for adding new names.
In iOS 7 I do it like this:
AddCharacterViewController *acvc = (AddCharacterViewController *)segue.destinationViewController;
acvc.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
acvc.modalPresentationStyle = UIModalPresentationCurrentContext;// so it does not take full screen in popover
And in AddCharacterVC I set content size like this:
- (void)viewWillAppear:(BOOL)animated {
CGSize size = CGSizeMake(320, 480); // size of view in popover
if (IDIOM == IPAD && [self respondsToSelector:#selector(preferredContentSize)]){
self.preferredContentSize = size;
}
And it works perfectly.
However, in iOS 8 modal view does not cover the whole popover, leaving TabBar visible. The user can tap on it or not, anyway modal view won't unwind properly.
I've tried:
setting acvc.modalPresentationStyle to UIModalPresentationOverCurrentContext
tried to set TabBar hidden
checked in storyboard that edges of TableVC extend under Bottom Bar and Bottom Bar in Modal View (AddCharacterVC) is set to none
All with no results.
Now the only thing I can think of is to try making modalPresentationStyleCustom and use UIPresentationController (I'm trying to do it now, but I haven't done it before). Am I missing something? Could there be other way to do it? Sorry, I cannot post images here yet. Many thanks in advance!
Ok, so I've set the modalPresentationStile to UIModalPresentationCustom, and used UIPresentationController - I've just copied code from WWDC-14's LookInside project and modified it a bit.
I'm not sure if it was the best solution, but it worked in my case.
The usual story -- I'm making an iOS 5/6 app run under iOS 7 and the navigation bar behavior change is causing a problem.
The app already worked like the iOS 7 default with a full-screen view and a translucent nav bar "over" of the view. The problem is that hiding/un-hiding the nav bar causes different behavior in iOS 7. On iOS 5/6 hiding/un-hiding the nav bar does not change the view. On iOS 7, hiding the bar visually moves the view up leaving a blank bar at the bottom of the screen and un-hiding the bar moves the view back down to occupy the full screen (with the nav bar on top, of course).
I need to continue to support iOS 5 so I don't use auto layout, but I do use the full screen.
I have a view in which I'm viewing a zoomable image -- so the view controller has a fullscreen view containing a scrollView which contains an imageView.
The status bar is always hidden.
I get to the view controller via a navigation controller so there is a (black, translucent) navigation bar which lies over the top of my fullscreen view/scrollView/imageView.
After a brief delay some overlaying labels fade and the navigation bar is hidden
A single tap restores the overlay labels and un-hides the navigation bar.
This works on iOS 5/6 -- the navigation bar slides off the top of the screen uncovering the top of the view/image.
On iOS 7, when the navigation bar slides off the top of the screen the entire view visually moves up a corresponding amount (i.e. 44 points) leaving a black bar at the bottom of the screen. I can see this by setting a background color on the top-level view and resizing the scrollview enough to see the background; the top of the view does indeed move offscreen and the background color is not drawn over the bottom (44 points) of the screen.
BUT, self.view.frame doesn't change and remains at {0, 0} 320 x height.
When I single-tap to restore the overlay info and navigation bar the view moves back down to occupy the full screen and the translucent nav bar is over the top of the view/image.
Nothing I've tried changes the behavior:
Changing the IB view controller layout controls (Under top bars, Under bottom bars, Adjust scroll view insets). Building for 5.1, 6.1, and 7.0 all produce the same result when run under 7.0.
self.edgesForExtendedLayout = UIRectEdgeNone
does nothing. Using the layout delta values doesn't do anything. In IB the view looks the same when "viewed as" iOS 7 and iOS 6 and earlier. I print out a lot of debug info but nothing about the view (or scroll view) seems to change when the view moves "off screen".
The code that shows the overlay info (run when the view is first shown and on single-taps) is:
- (void) showOverlayInfo {
self.navigationController.navigationBar.barStyle = UIBarStyleBlack;
[[[self navigationController] navigationBar] setTranslucent:YES];
[[self navigationController] setNavigationBarHidden:NO animated:NO];
overlayInfoHidden = NO;
overlayInfoFading = NO;
self.infoButton.hidden = NO;
self.infoButton.alpha = 1;
self.descriptionLabel.hidden = NO;
self.descriptionLabel.alpha = 1;
}
The code that hides the overlay info is:
- (void) hideOverlayInfo {
overlayInfoHidden = YES;
overlayInfoFading = NO;
self.infoButton.hidden = YES;
self.descriptionLabel.hidden = YES;
[[self navigationController] setNavigationBarHidden:YES animated:YES];
}
So can anybody tell me what (presumably simple) thing I'm missing?
I finally found my problem.
The key fact is that the image-viewer view controller was in a UIPageViewController,
so what I was looking at and experimenting with was really "inside" another view controller.
Although I had disabled the view controller setting Adjust Scroll View Insets for the image viewer VC, I hadn't done it for the containing VC that created the UIPageViewController and the UIPageViewController presents the pages in some subclass of a UIScrollView. When I changed them for the parent VC, the problem vanished.
So I think the moral of the story is to:
Think about the problem more globally when local doesn't work because maybe you're missing some important context.
If you don't want to use the iOS 7 behavior, change the settings for every single view controller you have!
I have the following layout (see below), which for most circumstances works just fine. The user is able to scroll within the blue UIScrollView (which for all intents and purposes is a UITableView, but this question generalises this), and then when they've reached the end of this scroll view, they can start scrolling again (they have to take their finger off, and on again, because the inner scroll view rubberbands otherwise), and the 'super' scroll view starts scrolling, revealing the rest of the image.
It's the whole (they have to take their finger off, and on again, because the inner scroll view rubberbands otherwise) that I don't want. Ideally, once the contained UIScrollView reaches the end of its content, I want the superview to take over scrolling straight away, so that the inner scroll view doesn't rubberband.
The same goes when the user is scrolling back up; when the red scrollview reaches the top of it's content, I want the inner blue scroll view to start scrolling up straight away, instead of the red scroll view rubberbanding at the top.
Any idea how? I am able to determine when the scroll views have reached the ends of their content, but I'm not sure how to apply this knowledge to achieve the effect I'm after. Thanks.
// Inner (blue) scroll view bounds checking
if (scrollView.contentOffset.y + scrollView.frame.size.height > scrollView.contentSize.height) { ... }
// Outer (red) scroll view bounds checking
if (scrollView.contentOffset.y < 0) { ... }
Yeah. Ive got a nifty trick for you.
Instead of having your red outlined view a scrollview make it a normal UIView that fills the screen. In that view lay out your scroll view (table view) and image view as they are in your illustration.
Place a scrollview that fills the bounds of the root view (i.e. also fills the screen) above all the other scrollview and image views. Set the content size of this view to be the total content height of all the views you want to scroll through. In otherwords there is an invisible scrollview sitting on top of all your other views and its content size height is inner scrollview (tableview) content size height + image view size height.
The heierarchy should look like this:
Then this scrollview on top that you have made with the really tall content size make its delegate be your view controller. Implement scrollViewDidScroll and we'll work some magic.
Scrollviews basically scroll by adjusting the bounds origin with funky formulas for momentum and stuff. So in our scrollviewDidScroll method we will simply adjust the bounds of the underlying views:
-(void)scrollViewDidScroll:(UIScrollView *)scrollView{
//the scroll view underneath (in the container view) will have a max content offset equal to the content height
//but minus the bounds height
CGFloat maxYOffsetForUnderScrollView = self.underScrollView.contentSize.height - self.underScrollView.bounds.size.height;
CGRect scrolledBoundsForContainerView = self.view.bounds;
if (scrollView.contentOffset.y <= maxYOffsetForUnderScrollView) {
//in this scenario we are still within the content for the underScrollView
//so we make sure the container view is scrolled to the top and set the offset for the contained scrollview
self.containerView.bounds = scrolledBoundsForContainerView;
self.underScrollview.contentOffset = scrollView.contentOffset;
return;
}
//in this scenario we have scrolled throug the entirety of the contained scrollview
//set its offset to the max and change the bounds of the container view to scroll everything else.
self.underScrollView.contentOffset = CGPointMake(0, maxYOffsetForUnderScrollView);
scrolledBoundsForContainerView.origin.y = scrollView.contentOffset.y - maxYOffsetForUnderScrollView;
self.containerView.bounds = scrolledBoundsForContainerView;
}
You will find that as scrollViewDidScroll is called every frame of animation that this faux scrolling of the contained views looks really natural.
But wait! I hear you say. That scroll view on top now intercepts ALL touches, and the views underneath it need to be touched as well. I have an interesting solution for that as well.
Set the scrollview on top to be off screen somewhere (i.e. set its frame off screen, but still the same size.) and then in your viewDidLoad method you add the scrollview's panGestureRecogniser to the main view. This will mean that you get all the iOS natural scrolling momentum and stuff without actually having the view on the screen. The contained scroll view will now probably go juddery as its pan gesture recognizer will get called as well (they work differently to UIEvent handling) so you will need to remove it.
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self.view addGestureRecognizer:self.scrollview.panGestureRecognizer];
[self.underScrollview removeGestureRecognizer:self.underScrollView.panGestureRecognizer];
//further code to set up content sizes and stuff
}
I had fun making this so heres a link to the sample project on github:
https://github.com/joelparsons/multipleScrollers
EDIT:
To show the scrollbar for the top scrollview when its off the screen no matter where you put it you can set the scrollIndicatorInsets to an inset created like this:
CGPoint scrollviewOrigin = self.scrollview.frame.origin;
self.scrollview.scrollIndicatorInsets = UIEdgeInsetsMake(-scrollviewOrigin.y,0,scrollviewOrigin.y,scrollviewOrigin.x);
*caveat that the scrollview still has to be the right height but I'm sure you get the idea.
And then to make the bar draw outside the scrollview's visible bounds you have to turn off clips to bounds
self.scrollview.clipsToBounds = NO;
OMG. jackslash, you saved my life.
In my case, I need to use three depth of scroll views.
Parent Scroll View
that has a scroll view as one of the children
Child Scroll View: shown as 'Explanation Scroll View / Review Table View / Information Scroll View' in the below image)
Hidden Scroll View
which is distribute own content offset to 'Parent Scroll View' and 'Child Scroll View'
which has content size of whole flatten contents
Screenshot of view hierarchy
After setting view hierarchy, I just need to sync whole flatten content size and distribute content offset properly.
Observable.combineLatest(
overviewStackView.rx.observe(CGRect.self, #keyPath(UIStackView.frame)).unwrap(),
explanationScrollView.rx.observe(CGSize.self, #keyPath(UIScrollView.contentSize)).unwrap()
)
.subscribe(onNext: { [weak self] overviewStackViewFrame, explanationScrollViewContentSize in
guard let self = self else { return }
let totalContentHeight = overviewStackViewFrame.height + self.segmentedControl.frame.height + explanationScrollViewContentSize.height
self.hiddenScrollView.contentSize.height = totalContentHeight
})
.disposed(by: disposeBag)
hiddenScrollView.rx.didScroll
.subscribe(onNext: { [weak self] _ in
guard let self = self else { return }
let currentHiddenScrollViewOffsetY = self.hiddenScrollView.contentOffset.y
let parentScrollViewMaxOffsetY = self.overviewStackView.frame.height
let expectedChildScrollViewOffsetY = max(currentHiddenScrollViewOffsetY - parentScrollViewMaxOffsetY, 0)
self.parentScrollView.contentOffset.y = min(parentScrollViewMaxOffsetY, currentHiddenScrollViewOffsetY)
self.explanationScrollView.contentOffset.y = expectedChildScrollViewOffsetY
})
.disposed(by: disposeBag)
I can't figure out how to actually use NSScrollview. I dragged the scroll view object onto an NSWindow in the interface builder. I then dragged some NSButtons onto the scroll view. My question is:
How do I actually make it scroll down, for example, 2x the original height?
Of course the user can scroll automatically using their UI. I assume what you want to do is to scroll programmatically.
A bit of background: An NSScrollView has a documentView, which is the underlying view that the scroll view shows a part of, and a clipView, which is the view that is shown on the screen. So the clip view is like a window into the document view. To scroll programmatically you tell the document view to scroll itself in the clip view.
You have two options on how to scroll programmatically:
- (void)scrollPoint:(NSPoint)aPoint –– This scrolls the document so the given point is at the origin of the clip view that encloses it.
- (BOOL)scrollRectToVisible:(NSRect)aRect –– This scrolls the document the minimum distance so the entire rectangle is visible. Note: This may not need to scroll at all in which case it returns NO.
So, for example, here is an example from Apple's Scroll View Programming Guide on how to scroll to the bottom of the document view. Assuming you have an IBOutlet called scrollView connected up to the NSScrollView in your nib file you can do the following:
- (void)scrollToBottom
{
NSPoint newScrollOrigin;
if ([[scrollview documentView] isFlipped]) {
newScrollOrigin = NSMakePoint(0.0,NSMaxY([[scrollview documentView] frame])
-NSHeight([[scrollview contentView] bounds]));
} else {
newScrollOrigin = NSMakePoint(0.0,0.0);
}
[[scrollview documentView] scrollPoint:newScrollOrigin];
}
At the moment I have an UIImageView inside a window. When the scene loads I save its position with:
imageView.center
The user can drag it around. On some occasions the item gets animated back to its original position which has been saved. This all works fine, when I hold the IPad vertically.
When I hold it horizontally though I cant move it back to that position, because in landscape mode I cant use the position which was saved in vertical orientation mode. this is because in Interface Builder I set the item to get auto sized relative to the borders (meaning if the item was in the center, then rotating the IPad it still stays in center)
So my question is: How can I get the correct original position the item would have in landscape orientation?
I tried to calculate the position by hand like this:
- (CGPoint)getHorizontalCoordinatesForPoint:(CGPoint)point
{
CGSize size = [[UIScreen mainScreen]applicationFrame].size;
CGFloat relativeX = (point.x / (size.width));
CGFloat relativeY = point.y / (size.height);
CGFloat horizontalScreenX = relativeX * (size.height);
CGFloat horizontalScreenY = relativeY * (size.width);
return CGPointMake(horizontalScreenX, horizontalScreenY);
}
but this position is a bit off. I think it is because I dont take into account the size of the navigation bar. Is there some converting function in ios which already does what I want?
Don't.
Rather, instead of saving [imageView center] when the scene loads, save it at key points:
-touchesBegan:withEvent: (or, if you are using gesture recognizers, in the gesture recognizer callback).
-didRotateFromInterfaceOrientation:
The minor complication is what happens if a user, say, is working in landscape, quits the app, rotates the portrait, and relaunches the app. In this case I would start the app in landscape and let the autorotation to portrait kick in and correct your center point.