So I found the following UI pattern online and I have been attempting to implement it in Xcode. However, I have been unsuccessful. I am unsure as to whether to create the optimal approach would be to
create three different UIViewControllers (in which case I am not sure as to how to get them to animate in and out of view/how to get them to overlap one another)
or to use a UITableView with custom overlapping cells. However, I am not sure whether this approach will allow me to animate properly upon pressing each cell. For this second approach, I saw this post, but this solution does not allow for touch interaction in the overlapping areas, something which I need.
I looked for libraries online that would allow for functionality such as this, but I was unsuccessful in finding any. The animation I am trying to achieve can be found here.
I would use a StackView to hold all the views of your ViewControllers.
Then you can set the stack view's spacing property to a negative value to make the views overlap. If you wish show the complete view of one of the view controllers on tap, you add a tap gesture recognizer to that view that changes the stackView's spacing for just that view to 0.
A really simple example:
class PlaygroundViewController: UIViewController {
let firstViewController = UIViewController()
let secondViewController = UIViewController()
let thirdViewController = UIViewController()
lazy var viewControllersToAdd = [firstViewController, secondViewController, thirdViewController]
let heightOfView: CGFloat = 300
let viewOverlap: CGFloat = 200
let stackView = UIStackView()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.white
firstViewController.view.backgroundColor = .red
secondViewController.view.backgroundColor = .blue
thirdViewController.view.backgroundColor = .green
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .vertical
stackView.spacing = -viewOverlap
viewControllersToAdd.forEach { (controller: UIViewController) in
if let childView = controller.view {
stackView.addArrangedSubview(childView)
NSLayoutConstraint.activate([
childView.heightAnchor.constraint(equalToConstant: heightOfView),
childView.widthAnchor.constraint(equalTo: stackView.widthAnchor)
])
let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(didTapChildView(sender:)))
childView.addGestureRecognizer(gestureRecognizer)
childView.isUserInteractionEnabled = true
}
addChild(controller)
controller.didMove(toParent: self)
}
view.addSubview(stackView)
NSLayoutConstraint.activate([
stackView.rightAnchor.constraint(equalTo: view.rightAnchor),
stackView.leftAnchor.constraint(equalTo: view.leftAnchor),
stackView.topAnchor.constraint(equalTo: view.topAnchor),
])
}
#objc func didTapChildView(sender: UITapGestureRecognizer) {
if let targetView = sender.view {
UIView.animate(withDuration: 0.3, animations: {
let currentSpacing = self.stackView.customSpacing(after: targetView)
if currentSpacing == 0 {
// targetView is already expanded, collapse it
self.stackView.setCustomSpacing(-self.viewOverlap, after: targetView)
} else {
// expand view
self.stackView.setCustomSpacing(0, after: targetView)
}
})
}
}
}
Related
Problem:
I am trying to create my own custom search field with a desired growing animation (if you click on it), and a shrinking animation when the user taps out.
The animation behaves weirdly since it moves out of the right screen bounds when shrinking, even though the text field/search bar's right anchor is not modified.
Like so:
Notice how the right side of the search bar briefly moves outside of the visible screen bounds during the animation.
Expected behavior:
The search bar should smoothly grow/shrink without moving the right edge position of the text field, i.e. have the right anchor stay pinned.
What you see in above gif is built using the following code (by subclassing a UITextField):
public class MySearchBar: UITextField {
private var preAnimationWidth: NSLayoutConstraint?
private var postAnimationWidth: NSLayoutConstraint?
public override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = Theme.GRAY800
self.borderStyle = .roundedRect
self.layer.masksToBounds = true
self.clipsToBounds = true
self.autocorrectionType = .no
self.font = FontFamily.ProximaNova.regular.font(size: 16)
self.textColor = .white
self.attributedPlaceholder = NSAttributedString(string: "Search", attributes: [.foregroundColor : Theme.GRAY400, .font: FontFamily.ProximaNova.regular.font(size: 16)])
// some further appearance configurations
}
public func setupGrowAnimation(initialWidth: NSLayoutConstraint, grownWidth: NSLayoutConstraint, height: CGFloat) {
preAnimationWidth = initialWidth
postAnimationWidth = grownWidth
self.layer.borderWidth = 0
self.layer.cornerRadius = height / 2
}
// growButton is called when the textfield becomes active, i.e. the user taps on it.
public func growButton() {
guard let preAnimationWidth = preAnimationWidth, let postAnimationWidth = postAnimationWidth else { return }
UIView.animate(withDuration: 0.2) {
preAnimationWidth.isActive = false
postAnimationWidth.isActive = true
self.layer.borderColor = Theme.GRAY600.cgColor
self.layer.borderWidth = 2
self.layer.cornerRadius = 8
self.layoutIfNeeded()
}
}
// shrinkButton is called whenever the textfield resigns its first responder state, i.e. the user clicks out of it.
public func shrinkButton() {
guard let preAnimationWidth = preAnimationWidth, let postAnimationWidth = postAnimationWidth else { return }
UIView.animate(withDuration: 0.2) {
postAnimationWidth.isActive = false
preAnimationWidth.isActive = true
self.layer.borderWidth = 0
self.layer.borderColor = .none
self.layer.cornerRadius = self.frame.height / 2
self.layoutIfNeeded()
}
}
}
And this is how the search bar is initialized in my viewDidLoad:
override func viewDidLoad() {
let containerView = UIView()
let searchBar = MySearchBar()
searchBar.addTarget(self, action: #selector(searchBarChangedEntry(_:)), for: .editingChanged)
searchBar.addTarget(self, action: #selector(searchBarEndedEditing(_:)), for: .editingDidEnd)
searchBar.translatesAutoresizingMaskIntoConstraints = false
let initialWidth = searchBar.widthAnchor.constraint(equalToConstant: 100)
let expandedWidth = searchBar.widthAnchor.constraint(equalTo: containerView.widthAnchor, constant: -32)
searchBar.setupGrowAnimation(initialWidth: initialWidth, grownWidth: expandedWidth, height: 44)
containerView.addSubview(searchBar)
stackView.insertArrangedSubview(containerView, at: 0)
NSLayoutConstraint.activate([
containerView.heightAnchor.constraint(equalToConstant: 44),
containerView.widthAnchor.constraint(equalTo: self.stackView.widthAnchor),
searchBar.heightAnchor.constraint(equalTo: containerView.heightAnchor),
initialWidth,
searchBar.rightAnchor.constraint(equalTo: containerView.rightAnchor, constant: -16)
])
self.stackView.setCustomSpacing(12, after: containerView)
}
The search bar is part of a container view which, in turn, is the first (top) arranged subview of a stack view covering the entire screen's safeAreaLayout rectangle
What I already tried:
I have to perform the animation using constraints, and I've tried to animate it without using the width anchor (e.g. by animating the leftAnchor's constant). Nothing worked so far.
Upon googling, I couldn't really find anything helpful that would help me find a solution to this problem, which is why I am trying my luck here.
I do have to admit that I am not proficient with animations of iOS at all - so please bear with me if this is a simple mistake to fix.
So, why does the search bar behave that way? And how can I fix this?
A little tough to say, because the code you posted is missing a lot of information (for example, you don't show the creation of the stackView, nor where its being added to the view hierarchy).
However, you might fix your issue with this simple change...
In both your growButton() and shrinkButton() funcs, change this line in the animation block:
self.layoutIfNeeded()
to this:
self.superview?.layoutIfNeeded()
Edit - a little explanation...
To animate constraint changes, we want to call .layoutIfNeeded() on the top-most view that will be affected.
When calling:
UIView.animate(withDuration: 0.5) {
self.someView.layoutIfNeeded()
}
we're telling auto-layout to calculate the changes and then generate and run an animation... but only for someView and its subviews.
If our action is going to affect someView.superview - or, for example, the constraint change is going to move/size a sibling of someView or a sibling of someView.superview, we haven't told auto-layout to include those views in its layout calculations.
I expect there are specific implementations / layout hierarchies where one would want to specifically exclude some views from the layout / animation... but...
Personally, I do this:
UIView.animate(withDuration: 0.5) {
self.view.layoutIfNeeded()
}
because the constraint I want to animate might be on a subview deep in the view hierarchy - and could have 4 or 5 or 6 etc superviews - all of which could be affected by the change.
Problem
I have a custom UIView that has an image and selection (border) subview. I want to be able to add this custom UIView as a subview of a larger blank view. Here's the catch, the larger blank view needs to clip all of the subviews to its bounds (clipToBounds). However, the user can select one of the custom UIViews within the large blank view, where the subview is then highlighted by a border.
The problem is that because the large blank view clips to bounds, the outline for the selected subview is cut off.
I want the image in the subview to clip to the bounds of the large blank view, but still be able to see the full selection outline of the subview (which is cut off due to the large blank view's corner radius.
I am using UIKit and Swift
đź‘Ž What I Currently Have:
đź‘Ť What I Want:
The image part of the subview clips to the bounds (corner radius) of the large blank view, but the outline selection view in the subview should not.
Thanks in advance for all your help!
I think what you are looking for is not technically possible as defined by the docs
From the docs:
clipsToBounds
Setting this value to true causes subviews to be clipped to the bounds of the receiver. If set to false, subviews whose frames extend beyond the visible bounds of the receiver are not clipped. The default value is false.
So the subviews do not have control of whether they get clipped or not, it's the container view that decides.
So I believe Matic's answer is right in that the structure he proposes gives you the most flexibility.
With that being said, here are a couple of work arounds I can think of:
First, set up to recreated your scenario
Custom UIView
// Simple custom UIView with image view and selection UIView
fileprivate class CustomBorderView: UIView
{
private var isSelected = false
{
willSet
{
toggleBorder(newValue)
}
}
var imageView = UIImageView()
var selectionView = UIView()
init()
{
super.init(frame: CGRect.zero)
configureImageView()
configureSelectionView()
}
required init?(coder: NSCoder)
{
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews()
{
super.layoutSubviews()
}
private func configureImageView()
{
imageView.image = UIImage(named: "image-test")
imageView.contentMode = .scaleAspectFill
addSubview(imageView)
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
imageView.topAnchor.constraint(equalTo: topAnchor).isActive = true
imageView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
imageView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
}
private func configureSelectionView()
{
selectionView.backgroundColor = .clear
selectionView.layer.borderWidth = 3
selectionView.layer.borderColor = UIColor.clear.cgColor
addSubview(selectionView)
selectionView.translatesAutoresizingMaskIntoConstraints = false
selectionView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
selectionView.topAnchor.constraint(equalTo: topAnchor).isActive = true
selectionView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
selectionView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
configureTapGestureRecognizer()
}
private func configureTapGestureRecognizer()
{
let tapGesture = UITapGestureRecognizer(target: self,
action: #selector(didTapSelectionView))
selectionView.addGestureRecognizer(tapGesture)
}
#objc
private func didTapSelectionView()
{
isSelected = !isSelected
}
private func toggleBorder(_ on: Bool)
{
if on
{
selectionView.layer.borderColor = UIColor(red: 28.0/255.0,
green: 244.0/255.0,
blue: 162.0/255.0,
alpha: 1.0).cgColor
return
}
selectionView.layer.borderColor = UIColor.clear.cgColor
}
}
Then in the view controller
class ClippingTestViewController: UIViewController
{
private let mainContainerView = UIView()
private let customView = CustomBorderView()
override func viewDidLoad()
{
super.viewDidLoad()
view.backgroundColor = .white
title = "Clipping view"
configureMainContainerView()
configureCustomBorderView()
mainContainerView.layer.cornerRadius = 50
mainContainerView.clipsToBounds = true
}
private func configureMainContainerView()
{
mainContainerView.backgroundColor = .white
view.addSubview(mainContainerView)
mainContainerView.translatesAutoresizingMaskIntoConstraints = false
mainContainerView.leadingAnchor.constraint(equalTo: view.leadingAnchor,
constant: 20).isActive = true
mainContainerView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor,
constant: 20).isActive = true
mainContainerView.trailingAnchor.constraint(equalTo: view.trailingAnchor,
constant: -20).isActive = true
mainContainerView.heightAnchor.constraint(equalToConstant: 300).isActive = true
view.layoutIfNeeded()
}
private func configureCustomBorderView()
{
mainContainerView.addSubview(customView)
customView.translatesAutoresizingMaskIntoConstraints = false
customView.leadingAnchor.constraint(equalTo: mainContainerView.leadingAnchor).isActive = true
customView.topAnchor.constraint(equalTo: mainContainerView.safeAreaLayoutGuide.topAnchor).isActive = true
customView.trailingAnchor.constraint(equalTo: mainContainerView.trailingAnchor).isActive = true
customView.bottomAnchor.constraint(equalTo: mainContainerView.bottomAnchor).isActive = true
view.layoutIfNeeded()
}
}
This gives me your current experience
Work Around 1. - Shrink subviews on selection
When the view is not selected, everything looks fine. When the view is selected, you could reduce the width and height of the custom subview with some animation while adding the border.
Work Around 2. - Manually clip desired subviews
You go through each subview in your container view and:
Apply the clipping to any subview you desire
Apply the corner radius to the views you clip
Leaving the container view unclipped and without a corner radius
To do that, I created a custom UIView subclass for the container view
class ClippingSubView: UIView
{
override var clipsToBounds: Bool
{
didSet
{
if clipsToBounds
{
clipsToBounds = false
clipImageViews(in: self)
layer.cornerRadius = 0
}
}
}
// Recursively go through all subviews
private func clipImageViews(in view: UIView)
{
for subview in view.subviews
{
// I am only checking image view, you could check which you want
if subview is UIImageView
{
print(layer.cornerRadius)
subview.layer.cornerRadius = layer.cornerRadius
subview.clipsToBounds = true
}
clipImageViews(in: subview)
}
}
}
Then make sure to adjust the following lines where you create your views:
let mainContainerView = ClippingSubView()
// Do this only after you have added all the subviews for this to work
mainContainerView.layer.cornerRadius = 50
mainContainerView.clipsToBounds = true
This gives me your desired output
This is a pretty common problem which may have multiple solutions. In the end though I always find it best to simply go one level higher:
ContainerView (Does not clip)
ContentView (Clips)
HighlightingView (Does not clip)
You would put all your current views on ContentView. Then introduce another view which represents your selection and put it on the same level as your ContentView.
In the end this will give you most flexibility. It can still get a bit more complicated when you add things like shadows. But again "more views" is usually the end solution.
You'll likely run into a lot of problems trying to get a subview's border to display outside its superView's clipping bounds.
One approach is to add an "Outline View" as a sibling of the "Clipping View":
When you select a clippingView's subview - and drag it around - set the frame of the outlineView to match the frame of that subview.
You'll want to set .isUserInteractionEnabled = false on the outlineView so it doesn't interfere with touches on the subviews.
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 3 years ago.
Improve this question
I want to open a view controller over an existing view controller via button click without using storyboards. How do I do this? Here is what I mean:
Let's say we have three view controllers I can scroll between:
"zeroVC", "oneVC", and "twoVC"
When I press a button on "twoVC" I want to now scroll between:
"zeroVC", "oneVC", and "threeVC"
I tried looking all through stack overflow but they all use storyboards.
Let's assume we have four view controllers: RedViewController, GreenViewController, BlueViewController, and the one to contain them all, ContainerViewController.
Although you mentioned a scrolling view controller with three children within, we'll make it a two screen setup to keep it simple.
The following approach is scalable, so you would easily adopt it with an arbitrary number of view controllers.
Our RedViewController is 7 lines long:
class RedViewController: UIViewController {
override func loadView() {
let view = UIView()
view.backgroundColor = .red
self.view = view
}
}
Before we move on to GreenViewController and BlueViewController, we will define protocol SwapViewControllerDelegate:
protocol SwapViewControllerDelegate: AnyObject {
func swap()
}
GreenViewController and BlueViewController will have a delegate that conforms to this protocol, which will handle the swapping.
We will make ContainerViewController conform to this protocol.
Note that SwapViewControllerDelegate has the AnyObject in its inheritance list to make it a class-only protocol–we can thus make the delegate weak, to avoid memory retain cycle.
The following is GreenViewController:
class GreenViewController: UIViewController {
weak var delegate: SwapViewControllerDelegate?
override func loadView() {
let view = UIView()
view.backgroundColor = .green
self.view = view
}
override func viewDidLoad() {
super.viewDidLoad()
let button = UIButton()
button.setTitle("Swap Me!", for: .normal)
button.setTitleColor(.black, for: .normal)
button.titleLabel?.font = .boldSystemFont(ofSize: 50)
button.addTarget(
self,
action: #selector(swapButtonWasTouched),
for: .touchUpInside)
view.addSubview(button)
// Put button at the center of the view
button.translatesAutoresizingMaskIntoConstraints = false
button.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
button.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
#objc private func swapButtonWasTouched(_ sender: UIButton) {
delegate?.swap()
}
}
It has weak var delegate: SwapViewControllerDelegate? which will handle the swap when the button added in viewDidLoad is touched, triggering the swapButtonWasTouched method.
BlueViewController is implemented likewise:
class BlueViewController: UIViewController {
weak var delegate: SwapViewControllerDelegate?
override func loadView() {
let view = UIView()
view.backgroundColor = .blue
self.view = view
}
override func viewDidLoad() {
super.viewDidLoad()
let button = UIButton()
button.setTitle("Swap Me!", for: .normal)
button.setTitleColor(.white, for: .normal)
button.titleLabel?.font = .boldSystemFont(ofSize: 50)
button.addTarget(
self,
action: #selector(swapButtonWasTouched),
for: .touchUpInside)
view.addSubview(button)
// Put button at the center of the view
button.translatesAutoresizingMaskIntoConstraints = false
button.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
button.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
#objc private func swapButtonWasTouched(_ sender: UIButton) {
delegate?.swap()
}
}
The only difference is the view's backgroundColor and the button's titleColor.
Finally, we'll take a look at ContainerViewController.
ContainerViewController has four properties:
class ContainerViewController: UIViewController {
let redVC = RedViewController()
let greenVC = GreenViewController()
let blueVC = BlueViewController()
private lazy var scrollView: UIScrollView = {
let scrollView = UIScrollView()
scrollView.translatesAutoresizingMaskIntoConstraints = false
scrollView.bounces = false
scrollView.isPagingEnabled = true
return scrollView
}()
...
}
scrollView is the view that will contain child view controllers, redVC, greenVC, and blueVC.
We will use autolayout, so don't forget to mark translatesAutoresizingMaskIntoConstraints as false.
Now, setup autolayout constraints of the scrollView:
class ContainerViewController: UIViewController {
...
private func setupScrollView() {
view.addSubview(scrollView)
let views = ["scrollView": scrollView]
[
NSLayoutConstraint.constraints(
withVisualFormat: "H:|[scrollView]|",
metrics: nil,
views: views),
NSLayoutConstraint.constraints(
withVisualFormat: "V:|[scrollView]|",
metrics: nil,
views: views),
]
.forEach { NSLayoutConstraint.activate($0) }
}
...
}
I used VFL, but you can manually set autolayou constraints as we did for the button above.
Using autolayout, we don't have to set contentSize of the scrollView ourselves.
For more information about using autolayout with UIScrollView, see Technical Note TN2154: UIScrollView And Autolayout.
Now the most important setupChildViewControllers():
class ContainerViewController: UIViewController {
...
private func setupChildViewControllers() {
[redVC, greenVC, blueVC].forEach { addChild($0) }
let views = [
"redVC": redVC.view!,
"greenVC": greenVC.view!,
"blueVC": blueVC.view!,
]
views.values.forEach {
scrollView.addSubview($0)
$0.translatesAutoresizingMaskIntoConstraints = false
$0.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
$0.heightAnchor.constraint(equalTo: view.heightAnchor).isActive = true
}
[
NSLayoutConstraint.constraints(
withVisualFormat: "H:|[redVC][greenVC]|",
options: .alignAllTop,
metrics: nil,
views: views),
NSLayoutConstraint.constraints(
withVisualFormat: "H:|[redVC][blueVC]|",
options: .alignAllTop,
metrics: nil,
views: views),
NSLayoutConstraint.constraints(
withVisualFormat: "V:|[redVC(==greenVC,==blueVC)]|",
metrics: nil,
views: views),
]
.forEach { NSLayoutConstraint.activate($0) }
[redVC, greenVC, blueVC].forEach { $0.didMove(toParent: self) }
greenVC.view.isHidden = true
greenVC.delegate = self
blueVC.delegate = self
}
...
}
We first add each of [redVC, greenVC, blueVC] as child view controllers of ContainerViewController.
Then add the view's of child view controllers to scrollView.
Set widthAnchor and heightAnchor of the child view controllers to be view.widthAnchor and view.heightAnchor, in order to make them fullscreen.
Moreover, this will also work when the screen rotates.
Using views dictionary, we use VFL to set autolayout constraints.
We will put greenVC.view on the right of redVC.view: H:|[redVC][greenVC]|, and similarly for the blueVC.view: H:|[redVC][blueVC]|.
To fix the vertical position of greenVC.view and blueVC.view, add .alignAllTop option to the constraints.
Then apply vertical layout for redVC.view, and set the height of the greenVC.view and blueVC.view: "V:|[redVC(==greenVC,==blueVC)]|.
The vertical position is set, as we used .alignAllTop while setting the horizontal constraints.
We should call didMove(toParent:) methods on the child view controllers after we add then as child view controllers.
(If you are wondering about what didMove(toParent:) and addChild(_:) methods do, apparently they do very little; see What does addChildViewController actually do? and didMoveToParentViewController and willMoveToParentViewController.)
Finally, hide greenVC.view, and set greenVC.delegate and blueVC.delegate to self.
Then of course, we need ContainerViewController to conform to SwapViewControllerDelegate:
extension ContainerViewController: SwapViewControllerDelegate {
func swap() {
greenVC.view.isHidden.toggle()
blueVC.view.isHidden.toggle()
}
}
That's it!
The entire project is uploaded here.
I recommend reading Implementing a Container View Controller, which is well-documented by Apple. (It is written in Objective-C, but it is actually straightforward to translate into Swift)
I want to stop the user interacting with a tableview while I get some data.
So I can show and remove a view such that:
var dimmingView: UIView?
Is added and removed with:
func showLoading() {
let dimmingView = UIView(frame: view.frame)
dimmingView.backgroundColor = .darkGray
view.addSubview(dimmingView)
let activityIndicator = UIActivityIndicatorView(style: .whiteLarge)
activityIndicator.translatesAutoresizingMaskIntoConstraints = false
dimmingView.addSubview(activityIndicator)
NSLayoutConstraint.activate([
activityIndicator.centerXAnchor.constraint(equalTo: dimmingView.centerXAnchor),
activityIndicator.centerYAnchor.constraint(equalTo: dimmingView.centerYAnchor)
])
activityIndicator.startAnimating()
self.dimmingView = dimmingView
}
And also
func removeLoading() {
dimmingView?.removeFromSuperview()
}
However, when added it moves along with the table (scrolls). If I stop the table scrolling when the new data is loaded it presents behind the UINavigationBar (the first cell at least). If I try to add to the navigationcontroller.view navigation.controller is nil (so I can't).
What is the best approach?
Is it possible to display an UIView on top of a container View?
I want to add a view with a few opacity background to still see my container View. But everything i tried made either my containerView disappear completely or on top of my View. I tried via Storyboard and code.
I'm sure I'm missing something.
just add your view to the view property of your container controller's container
simple:
let viewYouWantToAddSubviewTo = parent?.view
detail:
import UIKit
class CustomNavigationViewController: UINavigationController {
override func viewDidLoad() {
super.viewDidLoad()
setupViews()
}
func setupViews() {
let layout = UICollectionViewFlowLayout()
let rootVC = HomeCollectionViewController(collectionViewLayout: layout)
viewControllers = [rootVC]
let v = UIView()
v.backgroundColor = UIColor.blue
v.layer.opacity = 0.4
v.translatesAutoresizingMaskIntoConstraints = false
// add your view to this view of the controller's container
let vv = (parent?.view)!
vv.addSubview(v)
// constraints for v
v.leftAnchor.constraint(equalTo: vv.leftAnchor).isActive = true
v.rightAnchor.constraint(equalTo: vv.rightAnchor).isActive = true
v.topAnchor.constraint(equalTo: vv.topAnchor).isActive = true
v.bottomAnchor.constraint(equalTo: vv.bottomAnchor).isActive = true
}
}
result: