Creating a UIContainerView() programmatically and changing its height - ios

I'm creating a container view programmatically. However, i'm not able to change its height. I'm setting it programmatically but without any effect.
let supportView: UIView = UIView()
let containerView = UIView()
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
self.containerView.frame = CGRect(x: self.view.frame.size.width - 100, y: 200, width: 225, height: 70)
print(self.containerView.frame.height)
self.containerView.backgroundColor = UIColor.gray
self.containerView.layer.cornerRadius = 20
self.view.addSubview(self.containerView)
let controller = storyboard!.instantiateViewController(withIdentifier: "Storyboard2")
addChildViewController(controller)
containerView.addSubview(controller.view)
controller.didMove(toParentViewController: self)
}
I created the view controller with identifier "Storyboard2" in the storyboard. And i've set its height to 70 there too. But without any luck.
Any Help? Thanks

You have not set the clipsToBounds on containerView, and the default value of that property is false.
Add this line just under you are setting the containerView's frame:
containerView.clipsToBounds = true
Also, as some reading material, i would like to present to you this discussion about the clipsToBounds property.

Related

autoresizingMask not working as expected for UITableView.tableHeaderView's subview

I'm adding a header view to my UITableView and want to add a subview to it having some margins.
class ViewController: UIViewController {
private let tablewView = UITableView()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
tablewView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(tablewView)
[
tablewView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
tablewView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
tablewView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
tablewView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor)
].forEach{ $0.isActive = true}
let headerView = UIView(frame: CGRect(x: 0, y: 120, width: 200, height: 100))
headerView.backgroundColor = .blue
let subView = UIView(frame: CGRect(x: 10, y: 10, width: 180, height: 80))
subView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
subView.backgroundColor = .yellow
headerView.addSubview(subView)
tablewView.tableHeaderView = headerView
}
}
The problem is that the right margin isn't preserved when the header is resized (when the table view is laid out). As you can see on the image, the right margin is missing:
If I'm using the same view without UITableView, then the margin is preserved as expected.
Is it a UIKit bug? Are there any workarounds?
I know that I can try AutoLayout solutions from here Is it possible to use AutoLayout with UITableView's tableHeaderView? but they're looking a bit hacky. autoresizingMask is supposed to work, after all.
In Cocoa programming as in comedy, timing is everything.
Add the subview in a one-time implementation of viewDidLayoutSubviews and all will be well. The subview will appear correctly, and will continue working if the table view is resized (e.g. due to rotation of the interface).
So, cut these four lines:
let subView = UIView(frame: CGRect(x: 10, y: 10, width: 180, height: 80))
subView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
subView.backgroundColor = .yellow
headerView.addSubview(subView)
And instead:
var didLayout = false
override func viewDidLayoutSubviews() {
guard !didLayout else { return }
didLayout.toggle()
if let h = tablewView.tableHeaderView {
let subView = UIView(frame: h.bounds.insetBy(dx: 10, dy: 10))
subView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
subView.backgroundColor = .yellow
h.addSubview(subView)
}
}

Cannot Center Popover Swift

I am trying to make a popover that will allow users to swipe between images (is there a better approach to this than using a popover?).
Right now to make the popover, I have spend hours googling on simply how to make a rectangle center in the screen. From the internet my code is:
// get a reference to the view controller for the popover
let popController = UIStoryboard(name: "Event", bundle: nil).instantiateViewController(withIdentifier: "carouselPopover")
// set the presentation style
popController.modalPresentationStyle = UIModalPresentationStyle.popover
let width = view.frame.width
popController.preferredContentSize = CGSize(width: width, height: 300)
// set up the popover presentation controller
popController.popoverPresentationController?.permittedArrowDirections = UIPopoverArrowDirection.up
popController.popoverPresentationController?.delegate = self
popController.popoverPresentationController?.sourceView = self.view
popController.popoverPresentationController?.sourceRect = CGRect(x: view.bounds.midX, y: view.bounds.midY, width: 0, height: 0)
popController.popoverPresentationController?.permittedArrowDirections = UIPopoverArrowDirection(rawValue: 0)
// present the popover
self.present(popController, animated: true, completion: nil)
However, for the life of me I cannot understand why the popover is not centered
It only loses its center when I set the popController's preferred content size. Any thoughts?
TL:DR
I want to 1) center the popover on the screen, 2) Make the popover 1:1 ratio, and 3) make the width of the popover proportional to the width of the parent screen. How can I do that without 1000's lines of code.
You can create a custom popover class. Then you establish delegate patterns to determine if user swipes.
protocol PopoverDelegate: class {
func imageviewDidSwipe(_ popover: Popover)
}
class Popover: UIView {
weak var delegate: PopoverDelegate?
init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = UIColor.white
}
func setupImage(_ image: UIImage) {
let imageView = UIView(frame: CGRect.zero)
imageView.image = image
self.addSubview(imageView)
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
imageView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
imageView.heightAnchor.constraint(equalToConstant: 50).isActive = true // However big you want
imageView.widthAnchor.constraint(equalToConstant: 50).isActive = true // However big you want
}
func showPopover(over view: UIView) {
view.addSubview(self)
translatesAutoResizingMaskIntoConstraints = false
centerXAnchor.contraint(equalTo: view.centerXAnchor).isActive = true
centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
heightAnchor.constraint(equalToConstant: frame.height).isActive = true
widthAnchor.constraint(equalToConstant: frame.width).isActive = true
}
}
To use...
class vC: UIViewController {
func ImageOnClick() {
// change view to where you want popover to show on top of
let popover = Popover(frame: view.frame)
popover.setupImage(image.png)
popover.delegate = self
showPopover(over: view)
}
}
extension vC: PopoverDelegate {
func imageviewDidSwipe(_ popover: Popover) {
// image swiped
}
}

Modal viewcontroller subviews

I am presenting a view controller modally (transparent) and then trying to add a dialogue box as a subview with a few buttons. I would like this subview to be solid colour however and not transparent, but can't get it to work.
I have tried setting the alpha of the subview to 1 but it's not changing the appearance.
class GameOverViewController: UIViewController {
private let restart = UIButton(type: .custom)
private let mainmenu = UIButton(type: .custom)
override func viewDidLoad() {
//displays view controller modally
super.viewDidLoad()
self.view.backgroundColor = .white
self.view.alpha = 0.6
self.modalPresentationStyle = .overCurrentContext
//add dialogue box
let dialoguebox = UIView(frame: CGRect(origin: CGPoint(x: self.view.frame.width / 2, y: self.view.frame.height / 2), size: CGSize(width: self.view.frame.width / 2, height: self.view.frame.height / 2)))
dialoguebox.backgroundColor = .red
dialoguebox.center = self.view.center
dialoguebox.alpha = 1
self.view.addSubview(dialoguebox)
}
}
The problem is this line:
self.view.alpha = 0.6
That affects the alpha of this view and all its subviews, including your dialog box. You cannot make the dialog have full effective opacity, because it inherits its transparency from self.view.
What you probably meant to do is give self.view.backgroundColor some transparency. So do not make it pure .white; make it .white along with some lower alpha value.

Header of viewcontroller doesn't fit

So the original setup of the app that I inherited has the navigation bar set like so (this is in my AppDelegate):
private func configureNavigationController(_ navigationController: UINavigationController) {
navigationController.navigationBar.isTranslucent = false
self.window?.rootViewController = navigationController
let imageView = UIImageView(image: UIImage(named: "logo-white"))
imageView.contentMode = UIViewContentMode.scaleAspectFit
let center = (navigationController.topViewController?.view.frame.width)! / 2.0 - 44
let titleView = UIView(frame: CGRect(x: center, y: 0, width: 88, height: 44))
imageView.frame = titleView.bounds
titleView.addSubview(imageView)
UINavigationBar.appearance().barTintColor = UIColor.navBackground
UINavigationBar.appearance().tintColor = UIColor.white
UINavigationBar.appearance().addSubview(titleView)
}
This creates the nav bar across every view controller correctly with the image in the center, however I have some new functionality that needs to be on top of everything, and this logo file - logo-white - is still showing up over top.
That's the real problem I want to solve - so if my attempted solution below is wrong, let me know and tell me the correct way.
Anyway, I tried commenting out the code above in my AppDelegate, and putting it in the specific viewcontrollers that I need it for
override func viewDidLoad() {
super.viewDidLoad()
let imageView = UIImageView(image: UIImage(named: "logo-white"))
imageView.contentMode = UIViewContentMode.scaleAspectFit
let center = (navigationController!.topViewController?.view.frame.width)!// / 2.0 - 44
let titleView = UIView(frame: CGRect(x: center, y: 0, width: 88, height: 44))
imageView.frame = titleView.bounds
titleView.addSubview(imageView)
navigationItem.titleView = imageView
However this doesn't work - I can either get the logo to show up on the left side of the screen, or slightly off of center, but never center.
I am guessing that this is because the bar has a back button and a little settings icon as well on either side.
So, how do I do this correctly?
Is there a way to make it so that something can cover the logo? Is the solution to move it into my individual view controllers?
Here's a picture of the overlap here. The logo, "Pinyada" should not be covering this up at all - this is a third party library that should be on top of everything.
You may try this :
override func viewDidLoad() {
super.viewDidLoad()
let imageView = UIImageView(image: UIImage(named: "logo-white"))
imageView.contentMode = .scaleAspectFit
imageView.frame = CGRect(x: 0, y: 0, width: 88, height: 44)
navigationItem.titleView = imageView
}
If you have navigationItem.titleView , another titleview is not necessary.
Sometimes, if you need a much more precise control of the titleView, you can add a customTitleView. Add the following codes in the viewController and you can get what you want.
let imageView = UIImageView(image: UIImage(named: "logo-white"))
private func addTitleView(){
let nbar = (navigationController?.navigationBar)!
let width = nbar.frame.width
imageView.contentMode = UIView.ContentMode.scaleAspectFit
imageView.frame = CGRect.init(x: (width - 88) / 2.0 , y: 0, width: 88, height: 44)
nbar.addSubview(imageView)
}
private func removeTitleView(){
imageView.removeFromSuperview()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
addTitleView()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
removeTitleView()
}
BTW, UINavigationBar.appearance().addSubview(titleView)
This method will result in all navigationBar with the same titleView, which is not what you want.
I figured it out.
One of the other modifications to my header was taking up too much space. I made it smaller and the image fits perfectly.
I moved all of the code generating the header out of the AppDelegate and into my navigation protocol, where I then popped it into each and every viewcontroller.

Programmatically created shadow doesn't display

I have iOS app where I have a couple views with a shadow (Called them shadowView for convenience). This is how they're made:
let shadowView = UIView(frame: .zero)
self.addSubview(shadowView)
shadowView.layer.shadowColor = UIColor.black.cgColor
shadowView.layer.shadowOffset = CGSize(width: 0.5, height: 1.5)
shadowView.layer.shadowOpacity = 0.15
shadowView.layer.masksToBounds = false
shadowView.layer.cornerRadius = 8
shadowView.backgroundColor = .clear
shadowView.layer.shadowRadius = 5.0
shadowView.clipsToBounds = false
shadowView.layer.shadowPath = UIBezierPath(roundedRect: shadowView.bounds, cornerRadius: 20).cgPath
The view is then placed in position with Snapkit, but I left out that part of the code due to it being irrelevant. Now the shadow that I made here is not displayed. However, if I set the backgroundColor property to e.g. UIColor.yellow then the view itself shows, but not the shadow.
I also checked if the shadow might be cut off by any parent view, which doesn't seem to be the case.
As you can see it's not the usual clipsToBounds / masksToBounds mistake, and I've been looking at this for the last couple hours. Am I missing a piece of code maybe? Or did I miss anything?
Your frame size is .zero. Assign some valid size to it.
Give the view a non-zero frame size
let viewWidth = //any
let viewHeight = //any
let shadowView = UIView(frame: CGRect.init(x: 0, y: 0, width: viewWidth, height: viewHeight))
To try and clarify...
You are setting .shadowPath, but based on you code you're setting it prior to .shadowView having its frame set. So, you are creating a UIBezierPath with a frame of .zero -- and it never changes.
You need to set the shadow path either in viewDidLayoutSubviews of a view controller, or by overriding layoutSubviews() inside your custom view.
Here is a simple example that you can run in a Playground page:
import UIKit
import PlaygroundSupport
class TestViewController : UIViewController {
let shadowView = UIView(frame: .zero)
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(shadowView)
shadowView.layer.shadowColor = UIColor.black.cgColor
shadowView.layer.shadowOpacity = 0.15
shadowView.layer.cornerRadius = 8
shadowView.backgroundColor = .clear
// this would be handled by your use of SnapKit
shadowView.translatesAutoresizingMaskIntoConstraints = false
shadowView.topAnchor.constraint(equalTo: view.topAnchor, constant: 40.0).isActive = true
shadowView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20.0).isActive = true
shadowView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20.0).isActive = true
shadowView.heightAnchor.constraint(equalToConstant: 100).isActive = true
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
shadowView.layer.shadowPath = UIBezierPath(roundedRect: shadowView.bounds, cornerRadius: 20).cgPath
}
}
let vc = TestViewController()
vc.view.backgroundColor = .white
PlaygroundPage.current.liveView = vc
Like other colleagues said, the frame of your view is the problem.
This is a problem just because you set the layer's shadow path to be as big as the view's bounds that in that moment is .zero.
Still I understand that you want your view to react to its constraint when the auto-layout engine is ready to layout your views. There are then two things you can do to address this problem:
1) you can remove the explicit set of shadowView.layer.shadowPath and change the color of the view from clear to another color. This will cause the shadow to follow the shape of the view even if it changes, with a dynamic shadow path. While this will fix the issue, sometimes it results also in an impact on performance during animations and transitions.
2) if the view doesn't change its size during her life on screen you can force the superView to re-layout itself right after you add the shadowView as subview.
self.addSubview(shadowView)
self.setNeedsLayout()
self.layoutIfNeeded()
In this way when you'll create the shadow path bounds will not be zero because auto layout already did its magic changing the frame of your view.
Please note that with the second solution you will not see the view (that has clear color) but just its shadow.
Swift 4
Add this function in your view controller
func addShadow(myView: UIView?) {
myView?.layer.shadowColor = UIColor.black.cgColor
myView?.layer.shadowOffset = CGSize(width: 0, height:0)
myView?.layer.shadowOpacity = 0.3
myView?.layer.shadowRadius = 5
myView?.layer.masksToBounds = false
myView?.layer.cornerRadius = 12
}
and use it like:
let shadowView = UIView(frame: CGRect(x: 0, y: 0, width: yourWidth, height: yourHeight))
self.addShadow(shadowView)

Resources