Swift: Shadow not showing when view is added to window - swift

I am attempting to cast a shadow to my customView but it is not showing. This customView is added to the window using window?.addSubview(customView).
Implementation so far:
//CustomView setup
lazy var customView: UIView = {
let v = UIView()
v.translatesAutoresizingMaskIntoConstraints = false
v.layer.cornerRadius = 8
v.layer.shadowColor = UIColor.darkGray.cgColor
v.layer.shadowOffset = CGSize(width: 0, height: 10)
v.layer.shadowOpacity = 10.5
v.layer.shadowRadius = 15.0
v.layer.masksToBounds = true
return v
}()
//Adding view to window
window?.addSubview(customView)
NSLayoutConstraint.activate([
customView.leadingAnchor.constraint(equalTo: window!.leadingAnchor),
customView.trailingAnchor.constraint(equalTo: window!.trailingAnchor),
customView.heightAnchor.constraint(equalTo: window!.heightAnchor, multiplier: 1),
customView.topAnchor.constraint(equalTo: window!.safeAreaLayoutGuide.bottomAnchor, constant: -100)
])
I have followed advice from this post and this post, but somehow it doesn't show up for views added to window.

It's because of this line:
v.layer.masksToBounds = true
If you want shadow and corner rounding, I'd suggest using two layers, one that has the shadow and masksToBounds = false, and another one which is a child of the first and has corner rounding + masksToBounds = true

Related

Conforming cornerRadius of a subviews inside a UIView

I have a table view cell which consists of an imageView and a couple of labels. The image is confined to the top of the cell. In order to create the shadow effect, I have a background UIView which acts as the container for the image and labels. I have applied a cornerRadius of 6 to the background view.
You will notice that the bottom of the card has a corner radius applied but the image does not allow this for the top corners.
Here is my background UIView:
lazy var restaurantBackground : UIView = {
let view = UIView()
view.layer.cornerRadius = 6
view.backgroundColor = .white
return view
}()
How can I solve this? maskToBounds or clipToBounds for the background view does not work. It removes the shadow effect.
Addendum
// Adding the background view
contentView.addSubview(restaurantBackground)
restaurantBackground.translatesAutoresizingMaskIntoConstraints = false
restaurantBackground.topAnchor.constraint(equalTo: contentView.safeAreaLayoutGuide.topAnchor, constant: 4).isActive = true
restaurantBackground.leadingAnchor.constraint(equalTo: contentView.safeAreaLayoutGuide.leadingAnchor, constant: 4).isActive = true
restaurantBackground.trailingAnchor.constraint(equalTo: contentView.safeAreaLayoutGuide.trailingAnchor, constant: -4).isActive = true
restaurantBackground.bottomAnchor.constraint(equalTo: contentView.safeAreaLayoutGuide.bottomAnchor, constant: -4).isActive = true
// Adding shadow
restaurantBackground.layer.shadowColor = UIColor.black.cgColor
restaurantBackground.layer.shadowOpacity = 0.6
restaurantBackground.layer.shadowRadius = 4
restaurantBackground.layer.shadowOffset = CGSize(width: 0, height: 2)
restaurantBackground.layer.shouldRasterize = true
restaurantBackground.layer.rasterizationScale = UIScreen.main.scale
// Adding restaurant image to background
restaurantBackground.addSubview(restaurantImage)
restaurantImage.translatesAutoresizingMaskIntoConstraints = false
restaurantImage.topAnchor.constraint(equalTo: restaurantBackground.topAnchor, constant: 0).isActive = true
restaurantImage.leadingAnchor.constraint(equalTo: restaurantBackground.leadingAnchor, constant: 0).isActive = true
restaurantImage.trailingAnchor.constraint(equalTo: restaurantBackground.trailingAnchor, constant: 0).isActive = true
restaurantImage.heightAnchor.constraint(equalTo: restaurantImage.widthAnchor, multiplier: 1.0/2.0).isActive = true
Your food image view needs to be a subview of the resturantBackground view with a border radius, otherwise its normal borders will be placed above the rounded background. Right now, I assume you're adding both views to the same superview.

Adding Google Maps to a View in Swift

I have the following dilemma.
I created a 2 view in my project ( GMContainer and GMHolder). I want GMHolder inside all his view to display the Google Maps map.
Unfortunate I am just able to add a Google Maps view in the middle of my entire view and can't make it inside a view I created. Also I don't know what to add to the mapView as frame. I tried creating a new CGRect and adding the value of the view, adding my view, but id doesn't seem to do the trick...
Any tips on how to change the last rows to make Google Maps display on the entirety of GMHolder?
let GMContainer : UIView = {
let view = UIView()
view.backgroundColor = .white
view.layer.cornerRadius = 25
return view
}()
var GMHolder : UIView = {
let view = UIView()
view.layer.cornerRadius = 25
return view
}()
func setupUI() {
GMContainer.translatesAutoresizingMaskIntoConstraints = false
GMHolder.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(GMContainer)
view.addSubview(GMHolder)
GMContainer.topAnchor.constraint(equalTo: categoryBox.bottomAnchor , constant: 32).isActive = true
GMContainer.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 32).isActive = true
GMContainer.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -32).isActive = true
GMContainer.heightAnchor.constraint(equalToConstant: 450).isActive = true
GMHolder.topAnchor.constraint(equalTo: GMContainer.topAnchor , constant: 40).isActive = true
GMHolder.leadingAnchor.constraint(equalTo: GMContainer.leadingAnchor, constant: 24).isActive = true
GMHolder.trailingAnchor.constraint(equalTo: GMContainer.trailingAnchor, constant: -24).isActive = true
GMHolder.heightAnchor.constraint(equalToConstant: 300).isActive = true
//GM View Code
var mapView : GMSMapView
let camera = GMSCameraPosition.camera(withLatitude: 37.321262, longitude: -122.378945, zoom: 10)
mapView = GMSMapView.map(withFrame: CGRect(x: GMHolder.frame.origin.x, y: GMHolder.frame.origin.y , width: 300, height: 300), camera: camera)
// mapView.center = self.view.center
view.addSubview(mapView)
GMHolder = mapView
Photo of simulator as you can see, it isn't bounded to the view.
Init inside the GMHolder everything, it seems to do the trick.
var GMHolder : GMSMapView = {
GMSServices.provideAPIKey(API_KEY)
let camera = GMSCameraPosition.camera(withLatitude: 37.321262, longitude: -122.378945, zoom: 12)
let view = GMSMapView.map(withFrame: CGRect.zero, camera: camera)
view.layer.cornerRadius = 25
view.backgroundColor = .black
return view
}()

Default missing Autolayout constraints when added programmatically

I forgot to add an x-component to my autolayout, but I was still able to see the view. I was wondering how/if autolayout generates default constraints when used programatically because in IB, there would be errors. No errors are printed in the debug console for this either.
I notice that when I do not specify an x-component, the view will always be left anchored to its parent view. Is there documentation which states what the default values are when a constraint is missing?
import UIKit
import PlaygroundSupport
//
class MyViewController : UIViewController {
override func loadView() {
let view = UIView()
view.backgroundColor = .white
self.view = view
}
override func viewDidLoad() {
super.viewDidLoad()
let outBox = UIView()
outBox.backgroundColor = UIColor.blue
view.addSubview(outBox)
outBox.translatesAutoresizingMaskIntoConstraints = false
outBox.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
outBox.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
outBox.widthAnchor.constraint(equalToConstant: 200).isActive = true
outBox.heightAnchor.constraint(equalToConstant: 200).isActive = true
let inBox = UIView(frame: CGRect(x: 100, y: 2000, width: 10, height: 10))
inBox.backgroundColor = UIColor.red
outBox.addSubview(inBox)
inBox.translatesAutoresizingMaskIntoConstraints = false
inBox.topAnchor.constraint(equalTo: outBox.topAnchor).isActive = true
inBox.bottomAnchor.constraint(equalTo: outBox.bottomAnchor).isActive = true
inBox.widthAnchor.constraint(equalToConstant: 25).isActive = true
// NO x-constraint component.. Should raise missing constraints error.
}
}
PlaygroundPage.current.liveView = MyViewController()
The key isn't in setting frame here
let inBox = UIView(frame: CGRect(x: 100, y: 2000, width: 10, height: 10))
but it's here
inBox.translatesAutoresizingMaskIntoConstraints = false
that line ignores the internal conversion of frame to constraints and defaults them to zero based , insuffieicent constraints don't mean you can't see the view , for example you can do the same in IB and still see the view with red border and after run also but it doesn't mean it's properly set , and this as finally constraints will be converted to frame so it's a coincidence regarding zero-based

Why doesn't Autolayout change a views frame

I created a view in a func of its superview
let black = NSView(frame: NSRect(x: 0, y: 0, width: 10, height: 10))
black.translatesAutoresizingMaskIntoConstraints = false
black.wantsLayer = true
black.layer?.backgroundColor = NSColor.black.cgColor
self.addSubview(black)
black.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -15).isActive = true
black.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 15).isActive = true
black.heightAnchor.constraint(equalToConstant: 115).isActive = true
black.widthAnchor.constraint(equalToConstant: 115).isActive = true
print ("origin (\(black.frame.origin.x),\(black.frame.origin.y)) size (\(black.frame.size.width),\(black.frame.size.height)) ")
The output is origin(0,0), size(10,10), these are the values the view was created with.
On screen the black view is positioned as expected origin:(15,15) size:(115,115).
Why is the frame not updated?
After setting the constraints you can update the frame of your view calling the same of layoutIfNeeded function for Cocoa:
black.layoutSubtreeIfNeeded()

textview shakes when resizing view

I'm resizing the view that a textview belongs to and the text shakes when the view either gets bigger or gets smaller.
Declaration of said text view:
lazy var textview: UITextView = {
let textView = UITextView()
textView.text = ""
textView.font = .systemFont(ofSize: 12, weight: UIFontWeightMedium)
textView.isScrollEnabled = false
textView.isEditable = false
textView.isSelectable = true
textView.isUserInteractionEnabled = true
textView.translatesAutoresizingMaskIntoConstraints = false
textView.textAlignment = .center
textView.textColor = .lightGray
textView.dataDetectorTypes = .link
return textView
}()
I'm resizing the view that it's in to fit the full screen like this
if let window = UIApplication.shared.keyWindow {
let statusBarHeight = UIApplication.shared.statusBarFrame.size.height
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveLinear, animations: {
self.frame = CGRect(x: 0, y: statusBarHeight, width: window.frame.width, height: window.frame.height - statusBarHeight)
self.layer.cornerRadius = 0
self.layoutIfNeeded()
}, completion: nil)
}
Upon doing so, the view expands perfectly but the textviews text does a bounce effect that makes the animation look extremely unprofessional... any advice?
Edit: It seems like when I remove the center text alignment option it works fine. How do I make it work with the text center aligned?
edit: I took another look at this and attempted to use the technique based in UIScrollView animation of height and contentOffset "jumps" content from bottom.
Here's a minimal working example with text view with centered text alignment which is working for me!
I'd recommend managing animations either to be all constraint based, or all frame based. I attempted a version where the animation is driven by updating the container view frame but it was starting to take too long to left it at this constraint based approach.
Hope this points you in the right direction :)
import UIKit
class ViewController: UIViewController {
lazy var textView: UITextView = {
let textView = UITextView()
textView.text = "testing text view"
textView.textAlignment = .center
textView.translatesAutoresizingMaskIntoConstraints = false
return textView
}()
lazy var containerView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
var widthConstraint: NSLayoutConstraint!
var topAnchor: NSLayoutConstraint!
override func viewDidLoad() {
view.backgroundColor = .groupTableViewBackground
// add container view and constraints
view.addSubview(containerView)
containerView.frame = view.bounds.insetBy(dx: 100, dy: 200)
containerView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
containerView.heightAnchor.constraint(equalToConstant: 100).isActive = true
// keep reference to topAnchor and width as properties to animate
topAnchor = containerView.topAnchor.constraint(lessThanOrEqualTo: view.topAnchor, constant: 100)
widthConstraint = containerView.widthAnchor.constraint(equalToConstant: 300)
topAnchor.isActive = true
widthConstraint.isActive = true
// add text view to container view and set constraints
containerView.addSubview(textView)
textView.leftAnchor.constraint(equalTo: containerView.leftAnchor).isActive = true
textView.rightAnchor.constraint(equalTo: containerView.rightAnchor).isActive = true
textView.topAnchor.constraint(equalTo: containerView.topAnchor).isActive = true
textView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor).isActive = true
}
#IBAction func toggleResize(_ sender: UIButton) {
sender.isSelected = !sender.isSelected
view.layoutIfNeeded()
widthConstraint.constant = sender.isSelected ? view.bounds.width : 300
topAnchor.constant = sender.isSelected ? 20 : 100
// caculate the textView content offset for starting position based on
// expected end position at end of the animation
let xOffset = (textView.bounds.width - widthConstraint.constant) / 2
textView.contentOffset = CGPoint(x: -xOffset, y: textView.contentOffset.y)
UIView.animate(withDuration: 1) {
self.view.layoutIfNeeded()
}
}
}