Shadow is not visible on some UI elements - ios

I have a helper method that helps me to add a shadow on all UI elements, and it works perfectly for the most of them, but on some of them, it doesn't.
For example, on this stackView below, there is no shadow when I run my app:
var container = UIStackView()
let stackView = UIStackView(arrangedSubviews: [button1, button2, button3])
stackView.axis = .vertical
stackView.distribution = .equalSpacing
stackView.alignment = .fill
container = stackView
self.view.addSubview(container)
container.snp.makeConstraints { make in
make.top.equalTo(view).offset(25)
make.right.equalTo(view).offset(-25)
make.width.equalTo(view.frame.width * 0.185)
make.height.equalTo(view.frame.width * 0.65)
}
container.layer.addShadow()
I have also tried to call this addShadow() for my buttons(button1, button2, button3), but didn't work.
I have tried different values in parameters of addShadow(), but...
Another example is where it WORKS, on my imageView:
let avatarImageView = UIImageView()
self.addSubview(avatarImageView)
avatarImageView.contentMode = .scaleAspectFit
avatarImageView.image = UIImage(named: imageName)
avatarImageView.snp.makeConstraints { (make) in
make.height.equalToSuperview()
make.width.equalToSuperview().multipliedBy(0.5)
make.left.equalToSuperview()
}
avatarImageView.layer.addShadow(opacity: 0.3, offsetWidth: 13, offsetHeight: 8)
Here is a shadow method(in extension of CALayer):
func addShadow(scale: Bool = true, opacity: Float = 0.4, offsetWidth: Int = 5, offsetHeight: Int = 5, radius: CGFloat = 5) {
masksToBounds = false
shadowColor = UIColor.black.cgColor
shadowOpacity = opacity
shadowOffset = CGSize(width: offsetWidth, height: offsetHeight)
shadowRadius = radius
shouldRasterize = true
rasterizationScale = scale ? UIScreen.main.scale : 1
}

See Apple's UIStackView documentation.
Managing the Stack View’s Appearance:
The UIStackView is a nonrendering subclass of UIView.
UIStackView just manages the position and size of its arranged views, background or shadow won't show.

Related

UIImageView and vector images: how to adjust for zooming to avoid blurry image

I have a vector image named Link in my asset catalog (in my case contained in a pdf, setup as preserve vector data) and I use an UIImageView to show this image in my view hierarchy:
let image = UIImage(named: "Link")
let imageView = UIImageView()
imageView.contentMode = .scaleAspectFit
imageView.image = image
And this is the simple view hierarchy:
As you can see I use .scaleAspectFit and when I set a frame for imageView the UIImageView renders the image in the desired resolution to look sharp on screen. So far so good.
In order to zoom an scroll I use an UIScrollView. After zooming in the image in the imageView appears blurry due to obviously having too low resolution for the higher zoom:
Is there any straight forward way to adjust UIImageView so that it renders the image in a higher resolution?
Discussion:
Using .scaleAspectFit the renderered image resolution resolution seems to depend on the frame set for imageView. So setting a bigger frame and scaling the view using its transform property works, but is also rather tedious.
Another way is manually rendering the image at an apporpriate solution from the vector image using UIGraphicsImageRenderer or similar. This is currenlty my preferred solution, but I am wondering if there is an easier way using UIImageView.
Here is sample code (just replace your main ViewController with this in a new project), which shows the blurry image when zooming in (just use any vector image named Link in your assets and choose render as "Template Image"):
class ViewController: UIViewController, UIScrollViewDelegate {
let contentView: UIView = UIView(frame: CGRect(x: 0.0, y: 0.0, width: 1000.0, height: 1000.0))
let scrollView: UIScrollView = {
let view = UIScrollView(frame: CGRect(x: 0.0, y: 0.0, width: 400.0, height: 400.0))
view.minimumZoomScale = 0.5
view.maximumZoomScale = 10.0
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
self.scrollView.delegate = self
self.contentView.backgroundColor = UIColor.green.withAlphaComponent(0.2)
// setup view hierarchy
self.scrollView.translatesAutoresizingMaskIntoConstraints = false
self.contentView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(self.scrollView)
self.scrollView.addSubview(self.contentView)
// constraints
self.scrollView.leftAnchor.constraint(equalTo: self.view.leftAnchor).isActive = true
self.scrollView.rightAnchor.constraint(equalTo: self.view.rightAnchor).isActive = true
self.scrollView.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
self.scrollView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
self.contentView.leftAnchor.constraint(equalTo: self.scrollView.leftAnchor).isActive = true
self.contentView.rightAnchor.constraint(equalTo: self.scrollView.rightAnchor).isActive = true
self.contentView.topAnchor.constraint(equalTo: self.scrollView.topAnchor).isActive = true
self.contentView.bottomAnchor.constraint(equalTo: self.scrollView.bottomAnchor).isActive = true
self.contentView.widthAnchor.constraint(equalToConstant: 10.0 * self.view.frame.width).isActive = true
self.contentView.heightAnchor.constraint(equalToConstant: 10.0 * self.view.frame.height).isActive = true
// setup imageView
let image = UIImage(named: "Link")
let imageView = UIImageView()
imageView.contentMode = .scaleAspectFit
imageView.image = image
imageView.tintColor = UIColor.black
imageView.translatesAutoresizingMaskIntoConstraints = false
self.contentView.addSubview(imageView)
// add constraints
imageView.centerXAnchor.constraint(equalTo: self.contentView.centerXAnchor).isActive = true
imageView.centerYAnchor.constraint(equalTo: self.contentView.centerYAnchor).isActive = true
imageView.widthAnchor.constraint(equalToConstant: 100.0).isActive = true
imageView.heightAnchor.constraint(equalToConstant: 100.0).isActive = true
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let zoomScale = 8.0
self.scrollView.contentOffset = CGPoint(x: 0.5 * (self.contentView.frame.width - self.scrollView.frame.width), y: 0.5 * (self.contentView.frame.height - self.scrollView.frame.height))
self.scrollView.zoomScale = zoomScale
}
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return self.contentView
}
}

is there a UIKit equivalent to SwiftUI's zstack?

I'm trying to create something like this. I've been working with SwiftUI recently so I know I could create that by adding an image, text and button (the I'm flexible text is the label for a button/NavigationLink) to a zstack. but I'm looking around trying to see if there's anyway to do that in UIKit. preferably without using storyboards. I'm open to a cocoapods library or whatever if that's what it takes. I've looked around and explored using SwiftUI to create the desired ZStack and then use it in my UIKit with a UIHostingController but because it involves a button/navigationlink. seeing as how the NavigationLink would require the destination to conform to a View, I wanted to ask around before converting even more of my project to swiftui. I was more hoping this project would be for giving me more experience building views in UIKit without storyboards so I'd prefer to do that instead of using SwiftUI. if that's possible I guess.
I've tried searching around but all my google searches involving UIButtons and images just link to posts about setting the image in a UIButton.
since you wanted to get more experience in creating views using UIKit, I've created a view that inherits from UIView that you can reuse. There's quite a lot of code to get the same result in UIKit. The code and output are provided below.
NOTE: Read the comments provided
Code
class ImageCardWithButton: UIView {
lazy var cardImage: UIImageView = {
let image = UIImageView()
image.translatesAutoresizingMaskIntoConstraints = false // To flag that we are using Constraints to set the layout
image.image = UIImage(named: "dog")
image.contentMode = .scaleAspectFill
return image
}()
lazy var gradientView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false // IMPORTANT IF YOU ARE USING CONSTRAINTS INSTEAD OF FRAMES
return view
}()
// VStack equivalent in UIKit
lazy var contentStack: UIStackView = {
let stack = UIStackView()
stack.translatesAutoresizingMaskIntoConstraints = false
stack.axis = .vertical
stack.distribution = .fillProportionally // Setting the distribution to fill based on the content
return stack
}()
lazy var titleLabel: UILabel = {
let label = UILabel()
label.textAlignment = .center
label.numberOfLines = 0 // Setting line number to 0 to allow sentence breaks
label.text = "Let your curiosity do the booking"
label.font = UIFont(name: "Raleway-Semibold", size: 20) // Custom font defined for the project
label.textColor = .white
return label
}()
lazy var cardButton: UIButton = {
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.backgroundColor = .white
button.setTitle("I'm flexible", for: .normal)
button.setTitleColor(.blue, for: .normal)
// button.addTarget(self, action: #selector(someObjcMethod), for: .touchUpInside) <- Adding a touch event and function to invoke
return button
}()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() {
self.addSubview(cardImage) // Adding the subview to the current view. i.e., self
// Setting the corner radius of the view
self.layer.cornerRadius = 10
self.layer.masksToBounds = true
NSLayoutConstraint.activate([
cardImage.leadingAnchor.constraint(equalTo: self.leadingAnchor),
cardImage.trailingAnchor.constraint(equalTo: self.trailingAnchor),
cardImage.topAnchor.constraint(equalTo: self.topAnchor),
cardImage.bottomAnchor.constraint(equalTo: self.bottomAnchor),
])
setupGradientView()
addTextAndButton()
}
private func setupGradientView() {
let height = self.frame.height * 0.9 // Height of the translucent gradient view
self.addSubview(gradientView)
NSLayoutConstraint.activate([
gradientView.leadingAnchor.constraint(equalTo: self.leadingAnchor),
gradientView.trailingAnchor.constraint(equalTo: self.trailingAnchor),
gradientView.bottomAnchor.constraint(equalTo: self.bottomAnchor),
gradientView.heightAnchor.constraint(equalToConstant: height)
])
// Adding the gradient
let colorTop = UIColor.clear
let colorBottom = UIColor.black
let gradientLayer = CAGradientLayer()
gradientLayer.colors = [colorTop.cgColor, colorBottom.cgColor]
gradientLayer.locations = [0.0, 1.0]
gradientLayer.frame = CGRect(
x: 0,
y: self.frame.height - height,
width: self.frame.width,
height: height)
gradientView.layer.insertSublayer(gradientLayer, at:0)
print(self.frame)
}
private func addTextAndButton() {
// Adding the views to the stackview
contentStack.addArrangedSubview(titleLabel)
contentStack.addArrangedSubview(cardButton)
gradientView.addSubview(contentStack)
NSLayoutConstraint.activate([
contentStack.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 20),
contentStack.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -20), // Negative for leading and bottom constraints
contentStack.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -20), // Negative for leading and bottom constraints
cardButton.heightAnchor.constraint(equalToConstant: 60)
])
cardButton.layer.cornerRadius = 30 // Half of the height of the button
}
}
Output
Important pointers
You can create the layout using constraints or frames. In case you are using constraints, it is important to set a views .translatesAutoresizingMaskIntoConstraints to false (You can read the documentation for it).
NSLayoutConstraint.activate([...]) Is used to apply an array of constraints at once. Alternatively, you can use:
cardImage.leadingAnchor.constraint(...)isActivated = true
for individual constraints
Manual layout of the views will sometimes require padding. So for this you will have to use negative or positive values for the padding based on the edge (side) of the view you are in. It's easy to remember to set the value of the padding in the direction of the centre of the view.
E.x., From the leading/left edge, you will need to add a padding of 10 towards the centre of the view or -10 from the right/trailing side towards the centre.

Adding UITextView and UIImageView to UIStackView

So, I added some text (UITextView) to my stackView and centered to the top. I also added a UIImageView which would sit nicely under my UITextView. Well it doesn't. For some reason the image covers the text completely. If I delete the image the text comes back up nice on the top center. Played a lot with the stack distribution and alignment but no luck. Not sure what I'm missing :(. Any help is appreciated!
I'm adding both the UITextView and UIIMageView as arrangedSubview to the stack.
Here is my code:
//stack
let stack: UIStackView = {
let stack = UIStackView()
stack.translatesAutoresizingMaskIntoConstraints = false
stack.axis = .vertical
stack.spacing = 5
stack.distribution = .fillProportionally
stack.alignment = .fill
return stack
}()
//text
fileprivate let title: UITextView = {
let title = UITextView()
title.translatesAutoresizingMaskIntoConstraints = false
title.contentMode = .scaleAspectFit
title.layer.cornerRadius = 10
title.backgroundColor = .darkGray
title.font = UIFont(name: "Megrim-Regular", size: 17)
title.textColor = .white
title.textAlignment = .center
return title
}()
//image
let image: UIImageView = {
let image = UIImageView()
image.image = UIImage(named: "demoPic.jpg")
image.translatesAutoresizingMaskIntoConstraints = false
image.frame = CGRect(x: 0, y: 0, width: 50, height: 50)
return image
}()
Hope this below may help,
I think your issue is relating to constraints applied to the stackview and the holder view. (See below)
Your UI Elements (TextView & Image) code seems to be fine (maybe the image will not be work with 50 width /50 height inside this particular stack view configuration. It will require a different approach IMO.
Nevertheless on my playground in order to see it, I just applied 2 constraints towards my container view in order to see your TextView well above your ImageView as you wanted.
Here is the playground I used to reproduce your issue, you can copy and paste it to see if it fits what you request.
import UIKit
import PlaygroundSupport
/// DEMO VIEW CLASS
final class DemoView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .white
}
required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented")
}
}
// YOUR UI CODE
//stack
let stack: UIStackView = {
let stack = UIStackView()
stack.translatesAutoresizingMaskIntoConstraints = false
stack.axis = .vertical
stack.spacing = 5
stack.distribution = .fillProportionally
stack.alignment = .fill
return stack
}()
//text
fileprivate let title: UITextView = {
let title = UITextView()
title.translatesAutoresizingMaskIntoConstraints = false
title.contentMode = .scaleAspectFit
title.layer.cornerRadius = 10
title.backgroundColor = .darkGray
title.font = UIFont(name: "Megrim-Regular", size: 17)
title.text = "TextView"
title.textColor = .white
title.textAlignment = .center
return title
}()
//image
let image: UIImageView = {
let image = UIImageView()
image.backgroundColor = .red
image.translatesAutoresizingMaskIntoConstraints = false
image.frame = CGRect(x: 0, y: 0, width: 50, height: 50)
return image
}()
// PLAYGROUND DEMO VIEW TO HOLD YOUR STACK VIEW
let demoView = DemoView(frame: CGRect(x: 0, y: 0, width: 350, height: 150))
stack.addArrangedSubview(title)
stack.addArrangedSubview(image)
demoView.addSubview(stack)
demoView.addConstraints(
NSLayoutConstraint.constraints(withVisualFormat: "H:|-0-[stackView]-0-|",
options: NSLayoutConstraint.FormatOptions(rawValue: 0),
metrics: nil,
views: ["stackView": stack])
)
demoView.addConstraints(
NSLayoutConstraint.constraints(withVisualFormat: "V:|-0-[stackView]-0-|",
options: NSLayoutConstraint.FormatOptions(rawValue: 0),
metrics: nil,
views: ["stackView": stack])
)
PlaygroundPage.current.liveView = demoView
Results: Your Text View is above the image center (ImageView just have a RED Background here).

UIImageview not getting rounded

I am trying to create a circular image and I have implemeted the right code but I do not know why it does not get rounded below is my code
lazy var profileImage: UIImageView = {
let image = UIImageView()
image.translatesAutoresizingMaskIntoConstraints = false
image.layer.borderColor = UIColor.blue.cgColor
image.layer.borderWidth = 1.5
image.image = UIImage(named: "prof.jpg")
image.contentMode = .scaleAspectFit
image.layer.cornerRadius = image.frame.size.width / 2
image.clipsToBounds = true
return image
}()
and my constraints are
fileprivate func layout() {
view.addSubview(profileImage)
NSLayoutConstraint.activate([
profileImage.centerXAnchor.constraint(equalTo: view.centerXAnchor),
profileImage.centerYAnchor.constraint(equalTo: view.centerYAnchor),
profileImage.heightAnchor.constraint(equalToConstant: 200),
profileImage.widthAnchor.constraint(equalToConstant: 200),
])
}
layout() is then added to viewDidLoad
You’re not giving your UIImageView a frame when it’s initialised so it uses .zero as a default. This means that when you access image.frame.size.width you are getting a value of 0.
What I would suggest is to move your image.layer.cornerRadius = image.frame.size.width / 2 into the viewDidLayoutSubviews override on your UIViewController class.
You could also create a custom class that subclasses UIImageView and implements the same logic. The override for UIImageView would be layoutSubviews.
Image doesn't have a frame *image.layer.cornerRadius = image.frame.size.width / 2* inside the closure.
So set the corner radius after the profileImage is set with its constraints
profileImage.layoutIfNeeded()
profileImage.layer.cornerRadius = profileImage.frame.size.width / 2

UIStackView adjust height to subviews

I want the UIStackView to resize to fit their subviews (UIImageView and UILabel in this case)
let headerView = UIStackView()
headerView.axis = .vertical
headerView.alignment = .center
headerView.distribution = .equalSpacing
headerView.spacing = 10
let headerImage = UIImageView(...)
headerImage.contentMode = .scaleAspectFill
headerImage.clipsToBounds = true
headerImage.frame = CGRect(x: 0, y: 0, width: tableView.frame.width, height: tableView.frame.width / 1.618)
let desciptionView = UILabel()
desciptionView.text = "Some very long text wrapping multiple lines..."
desciptionView.numberOfLines = 0
desciptionView.font = UIFont.preferredFont(forTextStyle: UIFontTextStyle.body)
headerView.addArrangedSubview(headerImage)
headerView.addArrangedSubview(desciptionView)
print(headerView.bounds) // always 0,0,0,0
print(headerView.frame) // always 0,0,0,0
tableView.tableHeaderView = headerView
(in this code height and width are 0)
How to implement the wanted behaviour?
The only way I have found to do this is by setting the content hugging and compression resistance priorities of the child views to be required, something like this:
let arrangedViews = [filterLabel, image]
.map { (view: UIView) -> UIView in
view.setContentHuggingPriority(.required, for: .horizontal)
view.setContentCompressionResistancePriority(.required, for: .horizontal)
return view }
let stack = UIStackView(arrangedSubviews: arrangedViews)
stack.axis = .horizontal
stack.distribution = .fill
stack.spacing = 8
With those set on the arranged subviews, do you do indeed (or at least I did) get the desired result.

Resources