Conforming cornerRadius of a subviews inside a UIView - swift

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.

Related

UIKit: Relative autolayout constraints programmatically

I'm using swift + UIKit to autolayout a view programatically...
private func autoLayout(for child: UIView, in parent: UIView, width inset: CGFloat = 0) {
parent.addSubview(child)
child.translatesAutoresizingMaskIntoConstraints = false
child.topAnchor .constraint(equalTo: parent.safeAreaLayoutGuide.topAnchor, constant: inset) .isActive = true
child.bottomAnchor .constraint(equalTo: parent.safeAreaLayoutGuide.bottomAnchor, constant: -inset).isActive = true
child.leadingAnchor .constraint(equalTo: parent.safeAreaLayoutGuide.leadingAnchor, constant: inset) .isActive = true
child.trailingAnchor.constraint(equalTo: parent.safeAreaLayoutGuide.trailingAnchor, constant: -inset).isActive = true
}
In one instance I'm calling the method on a UILabel (with a set font size: 24), but I'd like to add a height constraint that adjusts the parent view to be 2.4x the child's height. Something like this inside the method...
if let label = child as? UILabel {
let value = child.frame.height * 2.4
parent.heightAnchor.constraint(equalToConstant: value).isActive = true
}
If I use the equalToConstant: parameter, then the size will be calculated when the method is called, which happens before the views are added to a UIStackView and their sizes are adjusted (essentially if I set the parent's constant equal to say child.frame.height * 2.4 it will calculate when the height is still zero).
How can I achieve this dynamically?
All you need to do is create a height constraint on the parent that is 2.4 times the height constraint of the child (label).
Here's an updated version of your code:
private func autoLayout(for child: UIView, in parent: UIView, width inset: CGFloat = 0) {
parent.addSubview(child)
child.translatesAutoresizingMaskIntoConstraints = false
child.leadingAnchor .constraint(equalTo: parent.safeAreaLayoutGuide.leadingAnchor, constant: inset) .isActive = true
child.trailingAnchor.constraint(equalTo: parent.safeAreaLayoutGuide.trailingAnchor, constant: -inset).isActive = true
if child is UILabel {
// This alternate syntax for creating a constraint allows you to set a multiplier
NSLayoutConstraint(item: parent, attribute: .height, relatedBy: .equal, toItem: child, attribute: .height, multiplier: 2.4, constant: 0).isActive = true
child.centerYAnchor.constraint(equalTo: parent.safeAreaLayoutGuide.centerYAnchor).isActive = true
} else {
child.topAnchor .constraint(equalTo: parent.safeAreaLayoutGuide.topAnchor, constant: inset) .isActive = true
child.bottomAnchor.constraint(equalTo: parent.safeAreaLayoutGuide.bottomAnchor, constant: -inset).isActive = true
}
}
I made an assumption that for the label you just want the label to be centered in the parent in addition to the parent's height being 2.4 times the child height. Adjust as needed.

How to resize content view in UIScrollView programmatically?

Here is the issue: I need to add a view containing other subviews to a scroll view programmatically. In addition, I also need to make the frame of such a view to stick to the bounds of the main super view. The code below shows the approach I was trying to implement, but as you can see from the pictures below the 'contentView' is not updating its frame size when the screen is rotated. The initial code is taken from here for demonstration purposes. Any help would be greatly appreciated.
import UIKit
class TestViewController : UIViewController {
var contentViewSize = CGSize()
let contentView: UIView = {
let view = UIView()
view.backgroundColor = .magenta
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
let labelOne: UILabel = {
let label = UILabel()
label.text = "Scroll Top"
label.backgroundColor = .red
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
let labelTwo: UILabel = {
let label = UILabel()
label.text = "Scroll Bottom"
label.backgroundColor = .green
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
let scrollView: UIScrollView = {
let v = UIScrollView()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = .cyan
return v
}()
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
contentViewSize = view.bounds.size
labelTwo.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: (contentViewSize.width - labelTwo.frame.size.width - 16.0)).isActive = true
labelTwo.topAnchor.constraint(equalTo: contentView.topAnchor, constant: (contentViewSize.height - labelTwo.frame.size.height - 16.0)).isActive = true
}
override func viewDidLoad() {
super.viewDidLoad()
contentViewSize = view.bounds.size
view.backgroundColor = .yellow
self.view.addSubview(scrollView)
scrollView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 8.0).isActive = true
scrollView.topAnchor.constraint(equalTo: view.topAnchor, constant: 8.0).isActive = true
scrollView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -8.0).isActive = true
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -8.0).isActive = true
scrollView.addSubview(contentView)
contentView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor, constant: 16).isActive = true
contentView.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 16).isActive = true
contentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: 16).isActive = true
contentView.rightAnchor.constraint(equalTo: scrollView.rightAnchor, constant: 16).isActive = true
contentView.addSubview(labelOne)
labelOne.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16.0).isActive = true
labelOne.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 16.0).isActive = true
contentView.addSubview(labelTwo)
labelTwo.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: (contentViewSize.width - labelTwo.frame.size.width - 16.0)).isActive = true
labelTwo.topAnchor.constraint(equalTo: contentView.topAnchor, constant: (contentViewSize.height - labelTwo.frame.size.height - 16.0)).isActive = true
labelTwo.rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: -16.0).isActive = true
labelTwo.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -16.0).isActive = true
}
}
You made some odd changes to the code from the answer you linked to. Also, that answer is a little out-of-date.
Here's a better example. Assuming you want only vertical scrolling, this will:
add a Cyan scroll view, inset 8-pts on each side from the safe-area
add a Magenta "content view" to the scroll view, with 16-pts on each side constrained to the scroll view's contentLayoutGuide, with a width 32-pts less than the scroll view's frame (16-pts on each side)
add a label at top-left of the content view
add a label at bottom-right of the content view
constrain the bottom label 1500-pts below the top label (so it will scroll vertically)
Code:
class ScrollTestViewController : UIViewController {
let contentView: UIView = {
let view = UIView()
view.backgroundColor = .magenta
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
let labelOne: UILabel = {
let label = UILabel()
label.text = "Scroll Top"
label.backgroundColor = .red
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
let labelTwo: UILabel = {
let label = UILabel()
label.text = "Scroll Bottom"
label.backgroundColor = .green
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
let scrollView: UIScrollView = {
let v = UIScrollView()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = .cyan
return v
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .yellow
// add the scroll view
self.view.addSubview(scrollView)
// add contentView to scroll view
scrollView.addSubview(contentView)
// add two labels to contentView
contentView.addSubview(labelOne)
contentView.addSubview(labelTwo)
// respect safe-area
let g = view.safeAreaLayoutGuide
// if you want to ignore the safe-area (bad idea),
// use this instead
//let g = view!
//scrollView.contentInsetAdjustmentBehavior = .never
// we're going to constrain the contentView to the scroll view's content layout guide
let scg = scrollView.contentLayoutGuide
NSLayoutConstraint.activate([
// constrain scrollView Top / Leading / Trailing / Bottom to view (safe-area)
// with 8-pts on each side
scrollView.topAnchor.constraint(equalTo: g.topAnchor, constant: 8.0),
scrollView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 8.0),
scrollView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -8.0),
scrollView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -8.0),
// constrain contentView Top / Leading / Trailing / Bottom to scroll view's Content Layout Guide
// with 16-pts on each side
contentView.topAnchor.constraint(equalTo: scg.topAnchor, constant: 16.0),
contentView.leadingAnchor.constraint(equalTo: scg.leadingAnchor, constant: 16.0),
contentView.trailingAnchor.constraint(equalTo: scg.trailingAnchor, constant: -16.0),
contentView.bottomAnchor.constraint(equalTo: scg.bottomAnchor, constant: -16.0),
// if we only want vertical scrolling, constrain contentView Width
// to scrollView's Frame Layout Guide minus 32-pts (because we have 16-pts on each side)
contentView.widthAnchor.constraint(equalTo: scrollView.frameLayoutGuide.widthAnchor, constant: -32.0),
// constrain labelOne Top / Leading 16-pts to contentView Top / Leading
// (so it shows up at top-left corner)
labelOne.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 16.0),
labelOne.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16.0),
// constrain labelTwo Bottom / Trailing 16-pts to contentView Bottom / Trailing
// (so it shows up at bottom-right corner)
labelTwo.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16.0),
labelTwo.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -16.0),
// constrain labelTwo Top to labelOne Bottom + 1500-pts
// so we'll have some vertical scrolling to get to it
labelTwo.topAnchor.constraint(equalTo: labelOne.bottomAnchor, constant: 1500.0),
])
}
}

Remove space at the top of a UIScollView in Swift

I have created a ScrollView and a ImageView:
let scrollView: UIScrollView = {
let scroll = UIScrollView()
scroll.backgroundColor = UIColor.red
scroll.contentSize.height = 1234
scroll.translatesAutoresizingMaskIntoConstraints = false
return scroll
}()
let filmImageView: UIImageView = {
let imageView = UIImageView()
imageView.contentMode = .scaleAspectFill
imageView.backgroundColor = UIColor.blue
imageView.clipsToBounds = true
imageView.translatesAutoresizingMaskIntoConstraints = false
return imageView
}()
And I have added these to my view with constraints, as followed:
self.view.addSubview(scrollView)
scrollView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 0).isActive = true
scrollView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: 0).isActive = true
scrollView.topAnchor.constraint(equalTo: view.topAnchor, constant: 0).isActive = true
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0).isActive = true
scrollView.addSubview(filmImageView)
filmImageView.widthAnchor.constraint(equalToConstant: self.view.frame.width).isActive = true
filmImageView.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 0).isActive = true
filmImageView.heightAnchor.constraint(equalToConstant: 350).isActive = true
Everything works, except, on the iPhone X only, the imageView doesn't sit to the top of the screen. See the image below. (I made the scrollview background colour red so you can see)
I have tried adding the code to my scrollView.
scroll.contentInset = UIEdgeInsets.zero
scroll.scrollIndicatorInsets = UIEdgeInsets.zero
scroll.contentOffset = CGPoint(x: 0.0, y: 0.0)
Along with:
self.automaticallyAdjustsScrollViewInsets = false
But it makes no change. I have been unable to find anything on the internet on how to get my image to sit to the top of the scrollview for the iPhone X. All other iPhone devices display this fine.
for the scrollView:
setting contentInsets to "never" instead of "always" solved the problem for me

container view constraint issues programmatically

I am trying to include a container UIView right above my tabBar however it doesnt seem to be displayed on my view because what i believe to be constraint issues. I would like my container view to look so in the view.
However my container is not showing in the view at all.
Here is my code:
self.mapContainer.layer.cornerRadius = 8
self.mapContainer.backgroundColor = UIColor.cyan
self.mapContainer.translatesAutoresizingMaskIntoConstraints = false
self.mapContainer.autoresizingMask = [.flexibleWidth, .flexibleHeight]
view.addSubview(self.mapContainer)
//constraints of the map view
let heightTabBar = self.tabBarController?.tabBar.frame.size.height
self.mapContainer.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant : (heightTabBar)! + 220).isActive = true
self.mapContainer.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
//self.topContainer.heightAnchor.constraint(equalTo: view.heightAnchor).isActive = true
self.mapContainer.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.3).isActive = true
//self.mapContainer.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
self.mapContainer.leadingAnchor.constraint(equalTo: view.trailingAnchor, constant: 20).isActive = true
self.mapContainer.trailingAnchor.constraint(equalTo: view.leadingAnchor, constant: 40).isActive = true
where am I going wrong here to where my container is not displaying
?
Problem:
You push your view with a positive value at your bottom anchor outside of the view. That's the reason why you don't see it.
Solution:
Instead of adding with (equalTo: view.bottomAnchor, constant : (heightTabBar)! + 220) you should subtract with self.mapContainer.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant : -(heightTabBar! + 220)).isActive = true at your bottomAnchor. You should also make your leading anchor of the container depend on the leading anchor of the view, the same for the trailing anchor. Also you need to set a negative value for your constant to your trailing anchor to add padding on the right side. Keep in mind: If you want to push from top and left then add and if you want to push from right and bottom then subtract
This should help you (Hints are in the code comments):
import UIKit
class ViewController: UIViewController {
var mapContainer:UIView!
override func loadView() {
super.loadView()
view.backgroundColor = .white
self.mapContainer = UIView()
self.mapContainer.layer.cornerRadius = 8
self.mapContainer.backgroundColor = UIColor.cyan
self.mapContainer.translatesAutoresizingMaskIntoConstraints = false
self.mapContainer.autoresizingMask = [.flexibleWidth, .flexibleHeight]
view.addSubview(self.mapContainer)
//constraints of the map view
let heightTabBar = self.tabBarController?.tabBar.frame.size.height
// You need to subtract to push from the bottom of the view
self.mapContainer.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant : -(heightTabBar! + 220)).isActive = true
self.mapContainer.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
self.mapContainer.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.3).isActive = true
// Your leading anchor of map container should depend on the leading anchor of the view
self.mapContainer.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20).isActive = true
// Your trailingAnchor anchor of map container should depend on the trailingAnchor anchor of the view
// Here you need to subtract to push from the right
self.mapContainer.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20).isActive = true
}
}
Result should look similar to this:
Result on iPhone5S simulator:

How can I make my contents go below the navigation controller without adjusting the content y position programmatically

How can I make my contents go below the navigation controller without adjusting the content y position programmatically . The reason I ask that is because when I adjust the content using y position it look fine but when the device is rotated I get a white gap because the navigation bar height change.
My code
import UIKit
class ProfileViewController: UINavigationController {
let topProfileView = UIView()
override func viewDidLoad() {
view.backgroundColor = .white
topProfileView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(topProfileView)
topProfileView.backgroundColor = .red
topProfileView.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 0).isActive = true
topProfileView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 0).isActive = true
topProfileView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: 0).isActive = true
topProfileView.heightAnchor.constraint(equalToConstant: 100).isActive = true
topProfileView.topAnchor.constraint(equalTo: view.topAnchor, constant:44).isActive = true
}
You can do this with the use of auto layout.
Make the x and y position 0 of red view in auto layout
I present the view controller as a UINavigationController this will display the the nav bar