UIPageViewController UIImage not showing full screen - ios

I am making an application that is a tab-view controller. For the first tab, I have a UIPageViewController that is scrolling through pictures. Everything works properly, except for that the picture is not showing full screen. I have constraints set up for the image view to cover the whole view controller but when it gets loaded into the page view, it doesn't cover all the way to the bottom. The image gets cut off by the scrolling dot indicators and then it is just white.
I'm sure it is simple, but is there a way that the images will cover the full screen like I have the constraints set up to do?

There's quite a simple solution for this problem on that page: http://tunesoftware.com/?p=3363
It is done by overriding the viewDidLayoutSubviews method of UIPageViewController and basically doing two things:
Increasing the bounds of the UIScrollView (the content) inside the UIPageViewController by setting it's frame to the bounds of the page controller's view
Moving the UIPageControl element (the dots) to the front of the view hierarchy so that it's in front of the content
Here's the code he provides (in case the link gets broken):
import UIKit
class TSPageViewController: UIPageViewController {
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
var subViews: NSArray = view.subviews
var scrollView: UIScrollView? = nil
var pageControl: UIPageControl? = nil
for view in subViews {
if view.isKindOfClass(UIScrollView) {
scrollView = view as? UIScrollView
}
else if view.isKindOfClass(UIPageControl) {
pageControl = view as? UIPageControl
}
}
if (scrollView != nil && pageControl != nil) {
scrollView?.frame = view.bounds
view.bringSubviewToFront(pageControl!)
}
}
}
All you have to do after that, is:
If you set your page controller in the storyboard, then just go to the identity inspector pane and change its class to TSPageViewController
If you set your page controller in code, then just make sure the class you defined for it inherits the TSPageViewController class.

Here's the Objective-C version. Create a new class (2 files):
(1) BasePageViewController.h extending UIPageViewController
#import <UIKit/UIKit.h>
#interface BasePageViewController : UIPageViewController
#end
(2) BasePageViewController.m containing viewDidLayoutSubviews override
#import "BasePageViewController.h"
#implementation BasePageViewController
- (void) viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
UIScrollView *scrollView = nil;
UIPageControl *pageControl = nil;
for(UIView *view in self.view.subviews)
{
if([view isKindOfClass:[UIScrollView class]])
{
scrollView = (UIScrollView *)view;
}else if([view isKindOfClass:[UIPageControl class]])
{
pageControl = (UIPageControl *)view;
}
}
if(scrollView != nil && pageControl!=nil)
{
scrollView.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height);
[self.view bringSubviewToFront:pageControl];
}
}
#end

Related

How do I center an iOS UIView subview in the root view programmatically?

There are tons of UIView centering questions and answers but I did not find one that matches my criteria. I have a UIImageView subclass that is the subview of another (the same) UIImageView subclass. I need to be able to "reset" my UIImageView subclass so that with user interaction it is centered in the screen (the view of the root view controller).
Just doing this:
- (void)reset {
[self setCenter: self.superview.center];
}
...does not work because the center point is in the coordinate system of the superview, NOT the root view. The superview can be dragged far from the center of the root view.
Initially I looped thru the superview hierarchy to find the root view but that still didn't work, again because of the different coordinate systems and variety of scale that the user could apply to each UIImageView subclass at each level of the nested views.
I did not find a complete solution on SO, so I wrote my own and posted it here. Hopefully it will save others some time and SO spelunking.
To center a UIView in "the screen", you need to center it relative to the view of the root view controller. This is very useful if you are subclassing UIView or it's subclasses (ImageView for example), programmatically, and your superview is not the view of a view controller. Here is a method to center your view in the root view, regardless of where it lies in the view hierarchy:
// center the view in the root view
- (void)centerInRootview:UIView *view {
UIView *rootview = UIApplication.sharedApplication.keyWindow.rootViewController.view;
// if the view hierarchy has not been loaded yet, size will be zero
if (rootview.bounds.size.width > 0 &&
rootview.bounds.size.height > 0) {
CGPoint rootCenter = CGPointMake(CGRectGetMidX(rootview.bounds),
CGRectGetMidY(rootview.bounds));
// center the view relative to it's superview coordinates
CGPoint center = [rootview convertPoint:rootCenter toView:view.superview];
[view setCenter:center];
}
}
This uses the UIView.convertPoint:toView method to convert from the coordinate system of the root view to the coordinate system of the superview of your view.
I use this method in a UIView category so that it is available from any UIView subclass. Here is View.h:
//
// View.h - Category interface for UIView
//
#import <UIKit/UIKit.h>
#interface UIView (View)
- (void)centerInRootview;
#end
And View.m:
//
// View.m - Category implementation for UIView
//
#import "View.h"
#implementation UIView (View)
// center the view in the root view
- (void)centerInRootview {
// make sure the view hierarchy has been loaded first
if (self.rootview.bounds.size.width > 0 &&
self.rootview.bounds.size.height > 0) {
CGPoint rootCenter = CGPointMake(CGRectGetMidX(self.rootview.bounds),
CGRectGetMidY(self.rootview.bounds));
// center the view in it's superview coordinates
CGPoint center = [self.rootview convertPoint:rootCenter toView:self.superview];
[self setCenter:center];
}
}
#end
And here is a simplistic example of how to use it from a UIImageView subclass. ImageView.h:
//
// ImageView.h - Example UIImageView subclass
//
#import <UIKit/UIKit.h>
#interface ImageView : UIImageView
- (void)reset;
#end
And ImageView.m:
#import "ImageView.h"
#implementation ImageView
- (void)reset {
self.transform = CGAffineTransformIdentity;
// inherited from UIImageView->UIView (View)
[self centerInRootview];
}
#end

iOS11 UIToolBar Contentview

In iOS 11 buttons and text field are unresponsive being subviews of UIToolBar. Comparing view hierarchy to iOS 10 we see there is a _UIToolBarContentView over all subview of UIToolBar.
For instance, this new layout of the UIToolBar breaks slacktextviewcontroller
https://github.com/slackhq/SlackTextViewController/issues/604
Need a solution working in iOS 10/11.
To solve the problem for iOS11 (compatible with lower versions) you only need
to make layoutSubview right after UIToolBar was added as a subview to UI hierarchy.
In this case _UIToolbarContentView lowers to the first subview of UIToolBar, and you can
add all your subviews higher as before.
For example in ObjC,
UIToolbar *toolbar = [UIToolbar new];
[self addSubview: toolbar];
[toolbar layoutIfNeeded];
<here one can add all subviews needed>
The same problem happens with slacktextviewcontroller
I have solved this problem in my case. I rewrite the layoutSubviews method in subclass of UIToobar and change the userInteractionEnable of _UIToolbarContentView into NO.
- (void)layoutSubviews {
[super layoutSubviews];
NSArray *subViewArray = [self subviews];
for (id view in subViewArray) {
if ([view isKindOfClass:(NSClassFromString(#"_UIToolbarContentView"))]) {
UIView *testView = view;
testView.userInteractionEnabled = NO;
}
}
}
You can just use the hitTest(_:with:) method.
First, create a property contentView in UIToolbar:
open private(set) var contentView: UIView = UIView()
Then, make the contentView's frame the same as the UIToolbar's. For example:
contentView.frame = bounds
contentView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
addSubview(contentView)
Finally, override the hitTest(_:with:) method:
open override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if self.point(inside: point, with: event) {
if let hitTestView = contentView.hitTest(point, with: event) {
return hitTestView
} else {
return self
}
} else {
return nil
}
}
In this situation, if you want to customize a toolbar by simply adding additional views, you should add them to the contentView so they will be positioned appropriately.
The new UIToolbar object actively uses layout based on constraints, so it is better to override - (void)updateConstraints method. To present custom views over UIToolbar object it is better to subclass it and add custom container view:
- (UIView *)containerView
{
if (_containerView) {
return _containerView;
}
_containerView = [[UIView alloc] initWithFrame:self.bounds];
_containerView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
return _containerView;
}
Now you can safely add your custom views to the container view. To make the custom views responsive we need change the order of toolbar subviews after the constraints update:
- (void)updateConstraints
{
[super updateConstraints];
[self bringSubviewToFront:self.containerView];
}
Note, that if you are using UINavigationController with custom toolbar, you should force it to update its layout before adding your custom subviews.
In Swift with autolayout and code only, what worked for me was to do layout as malex mentions just before adding items, but after setting constraints.
Instantiate your toolbar
Add it to your view
Add constraints
toolbar.layoutIfNeeded()
toolbar.setItems([... (your items)], animated: true)
There is an odd way to do it.
[self.textInputbar sendSubviewToBack:[self.textInputbar.subviews lastObject]];

How do I set the size of UITableView as a subview in a Container View with Auto-layout

I currently have a UIViewController which contains a menu bar and container UIView.
On main view load the container has an embed segue with a separate main content UIViewController.
When I click a button in the menu bar I have a custom segue set up to load a UITableViewController into the UIView container using the following code
-(void) perform {
BlockDetailViewController *src = (BlockDetailViewController *)[self sourceViewController];
UIViewController *dst = (UIViewController *)[self destinationViewController];
//Clear view of all subviews
for (UIView *view in src.viewContainer.subviews) {
[view removeFromSuperview];
}
//save destination
src.currentViewController = dst;
//set placeholderOutlet to destination
[src.viewContainer addSubview:dst.view];
}
This loads the UITableViewController into the UIView fine, however, it seems the UITableViewController size is set as the default window size which means the bottom cells of the UITableViewController are hidden from view. You can see them if you drag up but when you let go the bounce takes them out of the view.
How do I make sure the size of the UITableViewController is the same size as the UIView it is being placed into?
Thanks
You just have to create a a CGRect (frame) and set it to the views frame:
dst.view.frame = CGRectMake( CGRectGetMinX(self.view.frame),
CGRectGetMinY(self.view.frame),
CGRectGetWidth(self.view.frame),
CGRectGetHeight(self.view.frame);
the values should be float values. This is just an example.
When you define dst, you can use CGRect to set its position, width and height.

Scroll to top with status bar tap

I've looked through various SO questions on the topic and I have not found a solution. I have a UIViewController with a UITableView and a UICollectionView. I want the UICollectionView to scroll to the top, when the user taps it.
The documents say if you have more than one UiScrollView subclass - you need to set them to no and the UiScrollView you want to scroll to the top, to yes.
So I wrote this bit of code to go through all my views:
for (UIScrollView *view in self.view.subviews) {
if ([view isKindOfClass:[UIScrollView class]]) {
view.scrollsToTop = NO;
}
}
self.collectionView.scrollsToTop = YES;
This way I am sure any subclass of UiScrollView has it's scrollsToTop property set to no.
However tapping on the status bar does not do anything.
Can someone tell me what I am missing here?
Thank you
It seems that you are only iterating through the subviews of your main view. Your UITableView may be nested inside another view. Try doing the following;
//in view did load
[self setScrollToTopFalse:self.view];
self.collectionView.scrollsToTop = YES;
-(void)setScrollToTopFalse:(UIView *)v
{
for (UIView * v1 in [v subviews]) {
if ([[v1 class]isSubclassOfClass:[UIScrollView class]]) {
((UIScrollView *)v1).scrollsToTop = NO;
}
[self setScrollToTopFalse:v1];
}
}

ScrollView not scrolling when dragging on buttons

I have a scroll view that used to scroll when it didn't have buttons all over it. Now it does, and when dragging the mouse (on simulator) nothing happens (i think because the buttons are being pushed). How can I make this right?
This is happening because UIButton subviews of the UIScrollView (I assume buttons are added as subviews in your case) are tracking the touches and not the scroll view. UIScrollView method touchesShouldCancelInContentView is the key here. According to its description: "The default returned value is YES if view is not a UIControl object; otherwise, it returns NO.", i.e. for UIControl objects (buttons), UIScrollView does not attempt to cancel touches which prevents scrolling.
So, to allow scrolling with buttons:
Make sure UIScrollView property canCancelContentTouches is set to YES.
Subclass UIScrollView and override touchesShouldCancelInContentView to return YES when content view object is a UIButton, like this:
- (BOOL)touchesShouldCancelInContentView:(UIView *)view
{
if ( [view isKindOfClass:[UIButton class]] ) {
return YES;
}
return [super touchesShouldCancelInContentView:view];
}
I founded this question looking for the swift solution for this problem, I "translated" it like this:
Swift 5
class UIButtonScrollView: UIScrollView {
override func touchesShouldCancel(in view: UIView) -> Bool {
if view.isKind(of: UIButton.self) {
return true
}
return super.touchesShouldCancel(in: view)
}
}
hope this could help
Swift 3 Solution
override func touchesShouldCancel(in view: UIView) -> Bool {
if view is UIButton {
return true
}
return super.touchesShouldCancel(in: view)
}
One thing to try if you're seeing this in a simulator is to run on an actual phone. I couldn't scroll in the simulator but no prob on my phone.
In my case, I solved it with this way.
in ViewDidLoad
self.scrollView.panGestureRecognizer.delaysTouchesBegan = self.scrollView.delaysContentTouches;
in .m
- (BOOL)touchesShouldCancelInContentView:(UIView *)view {
if ([view isKindOfClass:[UIControl class]]) return YES;
return NO;
}

Resources