Modal viewcontroller subviews - ios

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.

Related

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.

Swift iOS - How to line up a sublayer position to match the center of button text

I have a button that is text. I added a red background subLayer and I made the backgroundLayer's width and height bigger then the button text. I tried to center the background layer to the button using:
backgroundLayer.position = button.center
It's not centering. This is what I get:
I know I can set the background color and cornerRadius on the button directly but when I do it that way the red background hugs the text:
button.backgroundColor = UIColor.red
button.layer.cornerRadius = 10
I want the redbackground to be wider and taller then the text:
backgroundLayer.frame = CGRect(x: 0, y: 0, width: buttonTextSize.width + 10, height: buttonTextSize.height + 5
I would Photoshop an example but I don't have Photshop in front of me at the moment. This is the closest I can find. This is a button from Vimeo. They aren't using text but the backgroundLayer is much wider and taller then the button image and the backgroundLayer's position is aligned with the button's midX and midY:
How do I get the position of the background subLayer to line up with the center of the button's text?
let button: UIButton = {
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitle("Next", for: .normal)
button.setTitleColor(UIColor.white, for: .normal)
button.contentHorizontalAlignment = .center
button.titleLabel?.font = UIFont.systemFont(ofSize: 23)
return button
}()
let backgroundLayer: CALayer = {
let layer = CALayer()
layer.backgroundColor = UIColor.red.cgColor
layer.cornerRadius = 10
return layer
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.lightGray
view.addSubview(button)
button.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
button.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
let buttonText = button.titleLabel?.text
let buttonTextSize = (buttonText! as NSString).size(withAttributes: [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 23.0)])
// I added 10 points to the backgroundLayer's width and 5 points to the backgroundLayer's height so its wider then the text
backgroundLayer.frame = CGRect(x: 0, y: 0, width: buttonTextSize.width + 10, height: buttonTextSize.height + 5)
button.layer.insertSublayer(backgroundLayer, at: 0)
backgroundLayer.position = button.center
}
Here's a button that seems to look the way you want (of course you can adjust any parameters that don't suit your sensibilities):
This button is automatically red, corner-rounded, and considerably larger than its text (even when the button is positioned using auto layout).
Here's how it was achieved through a subclass:
class MyRedButton : UIButton {
override func layoutSubviews() {
super.layoutSubviews()
self.backgroundColor = .red
self.layer.cornerRadius = 10
}
override var intrinsicContentSize: CGSize {
var sz = super.intrinsicContentSize
sz.width += 30; sz.height += 30
return sz
}
}
Matt's upvoted answer is correct.
Two things he pointed out to me in the comments that I was initially doing wrong was.
I tried to set backgroundLayer.position = button.center. This is wrong because the button's center is based on the frame's center and not it's bounds center. I should've set the backgroundLayer.position to match the center of the button's bounds
I tried to set the backgroundLayer's position to the button's center in viewWillLayoutSubviews which he said the button's bounds weren't known yet so the backgroundLayer had no information to base it on. I was supposed to add the code to viewDidLayoutSubviews
Here's the code here:
// 1. add the code to viewDidLayoutSubviews
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let text = button.titleLabel?.text
let textSize = (text! as NSString).size(withAttributes: [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 23.0)])
backgroundLayer.frame = CGRect(x: 0, y: 0, width: textSize.width + 10, height: textSize.height + 5)
button.layer.insertSublayer(backgroundLayer, at: 0)
// 2. get the buttons bound's center by accessing it's midX and midY
let buttonMidX = button.bounds.midX
let buttonMidY = button.bounds.midY
let buttonBoundsCenter = CGPoint(x: buttonMidX, y: buttonMidY)
// 3. set the backgroundLayer's postion to the buttonBoundsCenter
backgroundLayer.position = buttonBoundsCenter
}
And it works:

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)

How we can add additional UILabels to UINavigation?

I would like to add a cycle view and a label to UINavigation. like this:
I can set a label to my UINavigation by this code:
if let navigationBar = self.navigationController?.navigationBar {
let firstFrame = CGRect(x: 300, y: 0, width: navigationBar.frame.width/2, height: navigationBar.frame.height)
let firstLabel = UILabel(frame: firstFrame)
firstLabel.text = "First"
navigationBar.addSubview(firstLabel)
}
but I have two problems by this code:
1.how to set x position correctly?
(to test I set 300 value, but this value show different position on different screen sizes)
2. how to set a cycle background to this label ?
You can add both of the view (red circle) and the label (number 16) programmatically as a subView to the button of the bar button item.
What you should do is:
Connect the button as an IBOutlet to its ViewController:
Make sure that the connected component is the UIButton, but NOT UIBarButtonItem.
As you can see, I called it btnMenu.
Create your views (red circle and number label) and add it to the btnMenu:
It should be similar to:
import UIKit
class ViewController: UIViewController {
//...
#IBOutlet weak var btnMenu: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
//...
// setup the red circle UIView
let redCircleView = UIView(frame: CGRect(x: 0, y: 0, width: 20, height: 20))
redCircleView.backgroundColor = UIColor.red
redCircleView.layer.cornerRadius = view.frame.size.width / 2
// setup the number UILabel
let label = UILabel(frame: CGRect(x: 4, y: 0, width: 20, height: 20))
label.textColor = UIColor.white
label.font = UIFont.systemFont(ofSize: 10)
label.text = "16"
// adding the label into the red circle
redCircleView.addSubview(label)
// adding the red circle into the menu button
btnMenu.addSubview(redCircleView)
//...
}
//...
}
And that's it!
UIButton is a subclass of UIView, meaning that you can add subviews to it (add​Subview(_:​)).
Output:
Hope this helped.

Creating a UIContainerView() programmatically and changing its height

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.

Resources