I am trying to place an UIPageViewController inside a UIView with no predetermined height. What I am trying to achieve is build a responsive In-app Message that has one or more components (buttons, texts, carousels, etc) that has its height determined by its children.
The modal window works fine with all components except the UIPageViewController. The UIView resizes based on whatever components I put into it, but not when I place an UIPageViewController. It seems like there are some constraints missing.
The general hierarchy is:
UIView (centered vertically on the screen with trailing and leading margins, no height)
| UIStackView (inside this UIStackView I place any number of components I wish)
| | UILabel (example component)
| | UIButton (example component)
| | UIPageViewController (example component)
| | | UIStackView (inside this UIStackView I place any other number of components)
| | | | UILabel (example component)
| | | | UIButton (example component)
…
The following log excerpt belongs to an In-app Message with these characteristics:
UIView (centered vertically on the screen with trailing and leading margins, no height)
| UIStackView (leading and trailing anchors equal to parent, top and bottom anchors equal to children)
| | UIPageViewController (will display bellow the first slide content)
| | | UIStackView
| | | | UIImageView
| | | | UILabel
| | | | UILabel
Logs:
(lldb) po [[0x7feeb7a78e30 superview ] recursiveDescription]
<UIView: 0x7feeb76c44f0; frame = (0 0; 334 0); layer = <CALayer: 0x600002afd1c0>>
| <_UIPageViewControllerContentView: 0x7feeb7a78e30; frame = (0 0; 334 0); clipsToBounds = YES; opaque = NO; autoresize = W+H; layer = <CALayer: 0x600002ae1500>>
| | <_UIQueuingScrollView: 0x7feeafbc6800; frame = (0 0; 334 0); clipsToBounds = YES; gestureRecognizers = <NSArray: 0x600002773600>; layer = <CALayer: 0x600002ae0e80>; contentOffset: {334, 0}; contentSize: {1002, 0}; adjustedContentInset: {0, -334, 0, -334}>
| | | <UIView: 0x7feeb7a79b30; frame = (0 0; 334 0); layer = <CALayer: 0x600002ae04a0>>
| | | <UIView: 0x7feeb7a79ca0; frame = (334 0; 334 0); layer = <CALayer: 0x600002ae0880>>
| | | | <UIView: 0x7feeb6f25990; frame = (0 0; 757 219.667); autoresize = W+H; layer = <CALayer: 0x600002aed700>>
| | | | | <UIStackView: 0x7feeb6fb7a10; frame = (0 0; 757 219.667); layer = <CALayer: 0x600002aed8c0>>
| | | | | | <UIImageView: 0x7feeb76ceb70; frame = (0 0; 757 131.667); clipsToBounds = YES; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x600002aff560>>
| | | | | | <UILabel: 0x7feeb76ced40; frame = (0 141.667; 757 29.6667); text = 'This is the first slide t...'; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x600000bddb30>>
| | | | | | <UILabel: 0x7feeb6fb7c60; frame = (0 181.333; 757 18.3333); text = 'This is the description f...'; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x600000bca0d0>>
| | | <UIView: 0x7feeb7a79e10; frame = (668 0; 334 0); layer = <CALayer: 0x600002ae0840>>
| | <UIPageControl: 0x7feeb76cb9b0; frame = (10 -25; 314 20); autoresize = W; userInteractionEnabled = NO; gestureRecognizers = <NSArray: 0x600002693180>; layer = <CALayer: 0x600002afdac0>>
| | | <_UIPageControlContentView: 0x7feeb6fb4b10; frame = (111.667 -3; 91 26); layer = <CALayer: 0x600002aed0c0>>
| | | | <_UIPageControlIndicatorContentView: 0x7feeb6fb73b0; frame = (14.3333 0; 62.6667 26); autoresize = H; layer = <CALayer: 0x600002aec080>>
| | | | | <_UIPageIndicatorView: 0x7feeb6f27570; frame = (0.166667 8.16667; 9.66667 9.66667); opaque = NO; userInteractionEnabled = NO; tintColor = UIExtendedGrayColorSpace 0.333333 1; layer = <CALayer: 0x600002ae5b00>>
| | | | | <_UIPageIndicatorView: 0x7feeb0809630; frame = (17.5 8.16667; 9.66667 9.66667); opaque = NO; userInteractionEnabled = NO; tintColor = UIExtendedGrayColorSpace 0.666667 1; layer = <CALayer: 0x600002a01000>>
| | | | | <_UIPageIndicatorView: 0x7feeb6cd34c0; frame = (35.5 8.16667; 9.66667 9.66667); opaque = NO; userInteractionEnabled = NO; tintColor = UIExtendedGrayColorSpace 0.666667 1; layer = <CALayer: 0x600002a01080>>
| | | | | <_UIPageIndicatorView: 0x7feeb6fc6100; frame = (53.1667 8.16667; 9.66667 9.66667); opaque = NO; userInteractionEnabled = NO; tintColor = UIExtendedGrayColorSpace 0.666667 1; layer = <CALayer: 0x600002ae0140>>
Here are two examples. The first is a working In-app Message with no UIPageViewController. The second is what I am trying to achieve, but as a working example on Android.
Working In-app Message without UIPageViewController
Working example of carousel on Android
Based on these logs, can anyone point me in the right direction of correcting the constraints? What happens is that the views higher in the hierarchy have 0 height, though their children have not. This results in a 0 height In-app Message.
Edit
I am adding the modal view as a subview to the main view and I add the UIPageViewController to the top controller when it is present in the hierarchy:
All is done via code. I am not adding a view controller, only a subview to my main view:
UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow];
if (keyWindow != nil) {
[keyWindow addSubview:self.parentView]; // self.parentView = blackish background + vertically centered UIView (the actual modal view) + UIStackView for the components
if (hasCarousel){
UIViewController *topController = [UIApplication getPresentedViewController];
if (topController != nil)
[topController embedViewController:pageViewController inView:carouselWrapper]; // carouselWrapper = UIView containing the pageViewController that is added to the UIStackView above
}
}
To provide an answer based on our comments...
A UIPageViewController loads and sets the view size of its "page" controllers. So, trying to do it the other way around - setting the Page View Controller's size to match the "page" size is a little tricky.
In addition, if we tried to do that for each "page," the frame would keep changing and would (likely) be a bad user experience.
We can, however, pre-load the pages and calculate the maximum page height to setup our controller.
Overview code would be:
MutableArray *pages = [NSMutableArray new];
// however we load and initialize our page controllers
[pages addObject:FirstPageVC()];
[pages addObject:SecondPageVC()];
// etc
CGFloat maxHeight = 0;
CGSize fitSize = CGSizeMake(_carouselWrapper.frame.size.width, UILayoutFittingCompressedSize.height);
for (UIViewController *vc in pages) {
// get size of page view
CGSize sz = [vc.view systemLayoutSizeFittingSize:fitSize withHorizontalFittingPriority:UILayoutPriorityRequired verticalFittingPriority:UILayoutPriorityDefaultLow];
maxHeight = MAX(sz.height, maxHeight);
}
// if using the built-in PageControl
// add height of PageControl + 1.5 (control is shown 1.5-pts below the view)
UIPageControl *c = [UIPageControl new];
maxHeight += [c sizeForNumberOfPages:1].height + 1.5;
// here we can set the height of the view frame for our PageViewController
I played around with it a bit and put a working example project here: https://github.com/DonMag/DynamicPGVC
Basic idea of what you showed, although I made the "parentView" (I call it an OverlayView) a separate view subclass to keep a lot of the activity out of the main view controller.
Here's how it looks:
Tapping "Label" button (the thin rectangle shows the frame of the "carouselView"):
Tapping "Image" button:
Tapping "Page View Controller" button - first "page" (The "pages" are simple - vertical stack view with title and content labels):
and second page, showing the max height of the content:
Related
only on iPhone 12 Pro Max iOS 14.4
When I click the back button on navigationBar
the touch event pass to the bottom collectionView cell
Any idea can fix this question?
Here is the hitTest on UIWindow's log when click back button
2021-06-30 15:55:27.724531+0800 FOURTRY[36793:4319245] hit test view ---- <UIButton: 0x7fee71054bb0; frame = (0 44; 44 44); opaque = NO; layer = <CAGradientLayer: 0x600003fb9400>>
2021-06-30 15:55:27.724987+0800 FOURTRY[36793:4319245] hit test view ---- <UIButton: 0x7fee71054bb0; frame = (0 44; 44 44); opaque = NO; layer = <CAGradientLayer: 0x600003fb9400>>
2021-06-30 15:55:27.725386+0800 FOURTRY[36793:4319245] hit test view ---- <UIView: 0x7fee6f72d140; frame = (0 0; 213.5 343.051); gestureRecognizers = <NSArray: 0x60000306f4e0>; layer = <CAGradientLayer: 0x600003fbd540>>
2021-06-30 15:55:27.725702+0800 FOURTRY[36793:4319245] hit test view ---- <UIView: 0x7fee6f72d140; frame = (0 0; 213.5 343.051); gestureRecognizers = <NSArray: 0x60000306f4e0>; layer = <CAGradientLayer: 0x600003fbd540>>
UIView: 0x7fee6f72d140 is the collectionView cell, Why happen this?
I am creating a table view using AutoLayout for iOS 7 - iOS 9.
I have created a single cell prototype with .xib interface builder.
Inside the cell implementation I have no implementation yet.
Despite the fact, that AutoLayout constraints seems to be provided correctly, I am obtaining errors like this:
Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one you don't want.
Try this:
(1) look at each constraint and try to figure out which you don't expect;
(2) find the code that added the unwanted constraint or constraints and fix it.
(
"<NSLayoutConstraint:0x7fad5a80c3b0 UILabel:0x7fad585a6c60.width == UILabel:0x7fad5ad9f2b0.width>",
"<NSLayoutConstraint:0x7fad5a84c0a0 UILabel:0x7fad5ad9f2b0.leading == UILabel:0x7fad585a6c60.trailing + 10>",
"<NSLayoutConstraint:0x7fad5862a140 UILabel:0x7fad585a6c60.leading == MyCustomTableViewCell:0x7fad5ad88560.leading + 10>",
"<NSLayoutConstraint:0x7fad58621c30 MyCustomTableViewCell:0x7fad5ad88560.trailing == UILabel:0x7fad5ad9f2b0.trailing + 10>",
"<NSLayoutConstraint:0x7fad5ae218c0 MyCustomTableViewCell:0x7fad5ad88560.width == 1>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x7fad5a84c0a0 UILabel:0x7fad5ad9f2b0.leading == UILabel:0x7fad585a6c60.trailing + 10>
Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKit/UIView.h> may also be helpful.
When the breakpoint is reached, line [[[0x7fad5ad9f2b0 superview] superview] recursiveDescription] displays:
<VideoAnalysisVideoInformationTableViewCell: 0x7fad5ad88560; baseClass = UITableViewCell; frame = (0 301; 1 35); autoresize = W; layer = <CALayer: 0x7fad585445a0>>
| <UITableViewCellContentView: 0x7fad585d9d60; frame = (0 0; 1 35); opaque = NO; gestureRecognizers = <NSArray: 0x7fad585a2d30>; layer = <CALayer: 0x7fad585e9b40>>
| | <UILabel: 0x7fad585a6c60; frame = (10 5; 145 25); text = 'Score'; opaque = NO; autoresize = RM+BM; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x7fad5850b030>>
| | <UILabel: 0x7fad5ad9f2b0; frame = (165 5; 145 25); text = '33'; opaque = NO; autoresize = RM+BM; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x7fad5ad98210>>
| <_UITableViewCellSeparatorView: 0x7fad5a8cf620; frame = (15 34; 305 1); layer = <CALayer: 0x7fad5a9121d0>>
It seems, that the content view size is incorrect. How that could happen? How could I fix this?
You have set wrong constraints.
Give constraints like this,
Title label - leading and top
Info label - trailing and top
and select both label and give equal width. hope this will help :)
Update :
give top,leading,trailing and fix height to both label.
and give equal width
I have used auto-layout and scrollview. I have created hierarchy in storyboard which looks as shown below :
Basically view has scrollview inside it and scrollview has another subview view1 inside it. Rest of the views are under view1.
While debugging one issue I am facing I show that self.scrollView.subviews prints 3 views. Out of them 2 are ImageViews. And those are not subview of scrollview as per the hierarchy in storyboard.
(lldb) po self.scrollView.subviews
<__NSArrayM 0xb66fe80>(
<UIView: 0xb74b110; frame = (0 0; 320 3240); autoresize = RM+BM; layer = <CALayer: 0xb74b170>>,
<UIImageView: 0xb7e61c0; frame = (313 476; 7 3); alpha = 0; opaque = NO; autoresize = TM; userInteractionEnabled = NO; layer = <CALayer: 0xb7e62a0>>,
<UIImageView: 0xb7e6350; frame = (314.5 3091.5; 3.5 36); alpha = 0; opaque = NO; autoresize = LM; userInteractionEnabled = NO; layer = <CALayer: 0xb7e6430>>
)
What can be wrong here? Ask for any detail you need.
The UIView on your console is this view
the 2 UIImageView's are the scroll indicators
And one cool thing to debug view hierarchy is recursiveDescription.
po [self.view recursiveDescription]
I want to be able to access the navigationController of MPMediaPickerController so that I can change 'navigationController.tabBarController.selectedIndex'. However it seems like I cannot access it. In iOS 7, the default tab for a MPMediaPickerController is the Playlists tab, but I want to change it to the Songs tab. I know it's possible because I see other apps like Rise Alarm Clock have done this, but I have no clue how they do it.
Here are the subviews for MPMediaPickerController:
<UIWindow: 0x14e812a0; frame = (0 0; 320 568); gestureRecognizers = <NSArray: 0x14e81a30>; layer = <UIWindowLayer: 0x14e81a80>>
| <UIView: 0x14ed3c10; frame = (0 0; 320 568); layer = <CALayer: 0x14ee8d50>>
| | <_UISizeTrackingView: 0x14d45050; frame = (0 0; 320 568); clipsToBounds = YES; autoresize = W+H; layer = <CALayer: 0x14d44d70>>
| | | <_UIRemoteView: 0x14d446c0; frame = (0 0; 320 568); transform = [0.5, -0, 0, 0.5, -0, 0]; userInteractionEnabled = NO; layer = <CALayerHost: 0x14d44020>>
MPMediaPickerController is a subclass of UIViewController, so you can just use the standard view controller methods to access its navigation controller or bar - .navigationController and tabBarController.
If you cannot access it via those methods you should assume the class is designed to not let you have access to them. It may well be possibly to get access to the underlying tab-bar by iterating through the various sub-views of the controller, but this is unadvisable.
I use the state restoration offered by the SDK.
I load all my view controllers from the storyboard. The initial view controller shows another view controller modally. Both have a restoration identifier.
The restoration works fine BUT: When I am in the modally presented view controller and press home (save the state). Then reopen the app and state restoration starts the following happens:
For a split second I see the initial view controller and then the screen changes to the modally presented controller which I actually want to see.
Why is this happening? The snapshot in the caches directory does show the correct screen (from the presented view controller).
You can reproduce the issue with this project. Add a breakpoint in the initial view controller's viewDidAppear method and when restoring state, you will see initial view controller's view in the screen and in view hierarchy:
(lldb) po [[UIWindow keyWindow] recursiveDescription]
<UIWindow: 0x155607f0; frame = (0 0; 320 480); autoresize = W+H; gestureRecognizers = <NSArray: 0x15560d20>; layer = <UIWindowLayer: 0x1555f4f0>>
| <UIView: 0x15542ad0; frame = (0 0; 320 480); autoresize = RM+BM; layer = <CALayer: 0x15542a70>>
| | <UIButton: 0x155432d0; frame = (137 269; 46 30); opaque = NO; autoresize = RM+BM; layer = <CALayer: 0x15543b30>>
| | | <UIButtonLabel: 0x15663dd0; frame = (0 6; 46 18); text = 'Button'; clipsToBounds = YES; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x1566da20>>
| | <_UILayoutGuide: 0x15542b60; frame = (0 0; 0 20); hidden = YES; layer = <CALayer: 0x15542fe0>>
| | <_UILayoutGuide: 0x15541060; frame = (0 480; 0 0); hidden = YES; layer = <CALayer: 0x155410d0>>
If you check view controller hierarchy, modally presented view controller is not in initial view controller presented property:
(lldb) po [[[UIWindow keyWindow] rootViewController] presentedViewController]
nil
and breakpoint is after (theoretically) state restoration. But later it will be set correctly.
That issue seems to happen when working with storyboards where you let the framework call [window makeKeyAndVisible] for you and it is happening after restoration.
If you add next line to application:willFinishLaunchingWithOptions:
[self.window makeKeyAndVisible];
it will fix it.