Masks in Swift have caused me to pull out all my hair, I'm now in the #baldclub.
I have a menu view that contains many sub views which also have many subviews. I need to mask the entire menu view so that the menu view can be animated to slide out under a button.
I have tried to simplify the problem with one container view and one subview and still have not been able to mask the views! Perhaps masks are afraid of auto-layout?
Here are the views:
let curtainMask: UIView = {
let view = UIView()
view.backgroundColor = .green
view.isUserInteractionEnabled = false
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
let curtainContainer: UIView = {
let view = UIView()
view.backgroundColor = .clear
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
let locationImage: UIImageView = {
let view = UIImageView()
//view.layer.cornerRadius = 100
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .clear
view.alpha = 1
view.image = UIImage(systemName: "mappin")?.withRenderingMode(.alwaysOriginal)
view.contentMode = .scaleAspectFit
return view
}()
Here is the code being run on the main thread:
view.addSubview(curtainContainer)
curtainContainer.backgroundColor = .lightGray
curtainContainer.anchor(top: nil, left: nil, bottom: nil, right: nil, centerX: view.centerXAnchor, centerY: view.centerYAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 300, height: 200)
curtainContainer.addSubview(locationImage)
locationImage.anchor(top: curtainContainer.topAnchor, left: curtainContainer.leftAnchor, bottom: curtainContainer.bottomAnchor, right: curtainContainer.rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
view.addSubview(curtainMask)
curtainMask.backgroundColor = .green
curtainMask.anchor(top: nil, left: nil, bottom: nil, right: nil, centerX: view.centerXAnchor, centerY: view.centerYAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 200, height: 100)
view.layoutSubviews()
view.setNeedsLayout()
curtainContainer.mask = curtainMask
This code crates a large gray rectangle with an image as a subview. The mask is a smaller rectangle inside the large gray rectangle. I would expect the curtainContainer view to be masked along with the image subview but the result is that nothing is drawn. Any ideas?
Related
I have created a UIScrollView inside of a UIViewController. I have added a UIView inside of my UIScrollView with one label inside of the UIView. The plan is to eventually add 3 very large UIButtons that is why I need a scroll view. I have not used storyboard and it is done all programmatically. I cannot get the UIScrollView to work.
class ChooseCategoryPostViewController: UIViewController, UIScrollViewDelegate {
let scrollView: UIScrollView = {
let sv = UIScrollView()
sv.backgroundColor = .green
return sv
}()
let myView: UIView = {
let view = UIView()
view.backgroundColor = .red
return view
}()
let test: UILabel = {
let label = UILabel()
label.text = "test label"
label.font = UIFont.boldSystemFont(ofSize: 15)
label.textAlignment = .center
return label
}()
override func viewDidLoad() {
view.addSubview(scrollView)
scrollView.addSubview(myView)
scrollView.anchor(top: topTitle.bottomAnchor, left: nil, bottom: nil, right: nil, paddingTop: 200, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: view.frame.width, height: 200)
myView.anchor(top: scrollView.topAnchor, left: scrollView.leftAnchor, bottom: scrollView.bottomAnchor, right: nil, paddingTop: 0, paddingLeft: 10, paddingBottom: 0, paddingRight: 0, width: 1700, height: 200)
myView.addSubview(test)
test.anchor(top: myView.topAnchor, left: nil, bottom: nil, right: myView.rightAnchor, paddingTop: 20, paddingLeft: 0, paddingBottom: 0, paddingRight: 20, width: 0, height: 0)
}
override func viewDidLayoutSubviews() {
scrollView.isScrollEnabled = true
// Do any additional setup after loading the view
scrollView.contentSize = CGSize(width: view.frame.width, height: 200)
}
You need to make sure that the width of the scroll view contentSize is greater than the width of the scroll view itself. Something as simple as the following should cause horizontal scrolling to happen:
override func viewDidLayoutSubviews() {
scrollView.isScrollEnabled = true
// Do any additional setup after loading the view
scrollView.contentSize = CGSize(width: view.frame.width * 2, height: 200)
}
I have created 5 custom buttons all in code, and they all show up perfectly; however, for some reason, there is no response when tapped like they are inactive. I have added the subviews in my ViewDidLoad and coded the constraints in a separate function that's not important I don't believe to this so let me know if that's needed to better your understanding.
The code:
let backgroundImageView: UIImageView = {
let imageView = UIImageView()
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.layer.masksToBounds = true
return imageView
}()
let transparentView: UIView = {
let view = UIView()
view.backgroundColor = UIColor.black
view.backgroundColor = view.backgroundColor?.withAlphaComponent(0.5)
return view
}()
let button1: UIButton = {
let button = UIButton(type: .custom)
button.addTarget(self, action: #selector(button1Pressed), for: .touchUpInside)
button.setImage(UIImage(named: "button"), for: .normal)
button.isUserInteractionEnabled = true
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
addSubviews()
setupViewConstraints()
}
func addSubviews() {
self.view.addSubview(backgroundImageView)
backgroundImageView.addSubview(transparentView)
transparentView.addSubview(button1)
}
func setupViewConstraints() {
backgroundImageView.anchor(top: view.topAnchor, left: view.leftAnchor, bottom: view.bottomAnchor, right: view.rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
transparentView.anchor(top: view.topAnchor, left: view.leftAnchor, bottom: view.bottomAnchor, right: view.rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
button1.anchor(top: nil, left: nil, bottom: view.bottomAnchor, right: nil, paddingTop: 0, paddingLeft: 0, paddingBottom: 20, paddingRight: 0, width: 80, height: 80)
button1.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
}
#objc func button1Pressed() {
print("Button1 pressed")
}
By default, user interaction is disabled in UIImageView instances. And when a superview's user interaction is disabled, its subviews can't receive the user interaction, even if you have set button.isUserInteractionEnabled = true
So add imageView.isUserInteractionEnabled = true in backgroundImageView closure
let backgroundImageView: UIImageView = {
let imageView = UIImageView()
imageView.isUserInteractionEnabled = true
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.layer.masksToBounds = true
return imageView
}()
I am trying to create a Scroll View. The view itself is correctly
displayed but it does not scroll, even though I added a view to it and
set the content size.
NOTE: I am not using storyboards but Constraints (AutoLayout) only.
Code:
let scrollView: UIScrollView = {
let sv = UIScrollView()
sv.isScrollEnabled = true
sv.backgroundColor = .brown
return sv
}()
override func viewDidLoad() {
super.viewDidLoad()
setupViews()
}
func setupViews() {
self.view.addSubview(scrollView)
scrollView.anchor(top: self.view.topAnchor, left: self.view.leftAnchor, bottom: self.view.bottomAnchor, right: self.view.rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
scrollView.contentSize = CGSize(width: self.view.frame.width, height: 2000)
print(scrollView.contentSize)
let view = UIView()
view.backgroundColor = .blue
scrollView.addSubview(view)
view.anchor(top: scrollView.topAnchor, left: scrollView.leftAnchor, bottom: scrollView.bottomAnchor, right: scrollView.rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
}
.anchor() is a custom extension I made in order to make constraining views easier.
Any help would be appreciated.
It's better not to mix autolayout with frame layout do
1- Comment
scrollView.contentSize = CGSize(width: self.view.frame.width, height: 2000)
2- set equal width constraint between the view inside the scrollview to the vc's view and a height constraint of 2000
For no confuse with vc's view change
let view = UIView()
to
let contentView = UIView()
then add these 2 constarints
contentView.widthAnchor.constraint(equalTo:self.view.widthAnchor).isActive = true
contentView.heightAnchor.constraint(equalToConstant:2000).isActive = true
I have a UIViewController? SingleEventController displaying Events. Because it has dynamic information to show, I chose to create a new class UIScrollView EventScrollView. I tried to realize that based on this very well explained
Answer.
I tried to apply the Pure Auto Layout Approach: Create another UIView contentView in it, which contains all the information and is anchored to all four anchors of the scrollView.
The contentSize of the scrollView is determined in the viewDidLayoutSubviews function in the SingleEventController.
My struggle is, that the rightAnchor seems to be faulty. All the information-Elements go over the border of the view, the word-wrapping for the larger UILabels does not work, and every Item layed out at the right anchor is gone. (For example the usernames in the UITableView nopePeopleTV which is a subview of the contentView.)
This looks like this:
Code in the SingleEventController:
view.addSubview(scrollView)
scrollView.anchor(top: view.topAnchor, left: view.leftAnchor, bottom: buttonViewDividerView.topAnchor, right: view.rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
override func viewDidLayoutSubviews() {
let heightOfAllObjects = scrollView.calculateHeightOfAllObjects()
scrollView.contentSize = CGSize(width: self.view.frame.width, height: heightOfAllObjects + scrollView.heightOfAllPaddings)
}
Code in EventScrollView:
addSubview(contentView)
contentView.addSubview(titleLabel)
contentView.addSubview(locationLabel)
...
contentView.addSubview(nopePeopleTV)
contentView.anchor(top: topAnchor, left: leftAnchor, bottom: bottomAnchor, right: rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
titleLabel.anchor(top: contentView.topAnchor, left: contentView.leftAnchor, bottom: nil, right: contentView.rightAnchor, paddingTop: 10, paddingLeft: padding, paddingBottom: 0, paddingRight: padding, width: 0, height: 0)
locationLabel.anchor(top: titleLabel.bottomAnchor, left: contentView.leftAnchor, bottom: nil, right: nil, paddingTop: 0, paddingLeft: padding, paddingBottom: 0, paddingRight: 0, width: 200, height: 0)
nopePeopleTV.anchor(top: maybePeopleTV.bottomAnchor, left: contentView.leftAnchor, bottom: nil, right: contentView.rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
layoutIfNeeded()
contentView.layoutIfNeeded()
What am I doing wrong?
My anchor function looks like this:
func anchor(top: NSLayoutYAxisAnchor?, left: NSLayoutXAxisAnchor?, bottom: NSLayoutYAxisAnchor?, right: NSLayoutXAxisAnchor?, paddingTop: CGFloat, paddingLeft: CGFloat, paddingBottom: CGFloat, paddingRight: CGFloat, width: CGFloat, height: CGFloat) {
translatesAutoresizingMaskIntoConstraints = false
if let top = top {
topAnchor.constraint(equalTo: top, constant: paddingTop).isActive = true
}
if let left = left {
leftAnchor.constraint(equalTo: left, constant: paddingLeft).isActive = true
}
if let bottom = bottom {
bottomAnchor.constraint(equalTo: bottom, constant: -paddingBottom).isActive = true
}
if let right = right {
rightAnchor.constraint(equalTo: right, constant: -paddingRight).isActive = true
}
if width != 0 {
widthAnchor.constraint(equalToConstant: width).isActive = true
}
if height != 0 {
heightAnchor.constraint(equalToConstant: height).isActive = true
}
}
The problem is that you are letting the content view width be dictated from the inside out by the width of the subviews. You need to give the content view a width constraint matching the width of the scroll view itself.
I'll demonstrate the difference with an artificial but complete code-only example:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let sv = UIScrollView()
sv.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(sv)
NSLayoutConstraint.activate([
sv.topAnchor.constraint(equalTo: self.view.topAnchor),
sv.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
sv.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
sv.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
])
let cv = UIView() // content view
cv.translatesAutoresizingMaskIntoConstraints = false
sv.addSubview(cv)
NSLayoutConstraint.activate([
sv.topAnchor.constraint(equalTo: cv.topAnchor),
sv.leadingAnchor.constraint(equalTo: cv.leadingAnchor),
sv.trailingAnchor.constraint(equalTo: cv.trailingAnchor),
sv.bottomAnchor.constraint(equalTo: cv.bottomAnchor),
])
let lab = UILabel()
lab.translatesAutoresizingMaskIntoConstraints = false
lab.numberOfLines = 0
lab.text = Array(repeating: "xxx", count: 100).joined(separator: " ")
cv.addSubview(lab)
NSLayoutConstraint.activate([
lab.topAnchor.constraint(equalTo: cv.topAnchor, constant:30),
lab.leadingAnchor.constraint(equalTo: cv.leadingAnchor),
lab.trailingAnchor.constraint(equalTo: cv.trailingAnchor),
])
}
}
Here's the result:
We have a very long label, but it doesn't wrap: it keeps extending to the right, off the screen. That's because the content view itself has its right edge off the screen. That's because we have done nothing to prevent this from happening. The long label is itself making the content view as wide as the label's entire text.
Now I add one final line of code to viewDidLoad:
cv.widthAnchor.constraint(
equalTo:sv.frameLayoutGuide.widthAnchor).isActive = true
The result is this:
The content view is now the width of the scroll view, so the label wraps within the scroll view.
We do not, however, do the same thing to the height of the content view; we want it to grow vertically in accordance with its subviews. And that way, we will be able to scroll vertically (which is what you want) but not horizontally (which is also what you want).
I have a UIView() added to UIApplication.shared.keyWindow so it covers the screen in totality.
To that UIView I add as sublayer a gradient color (which I think it's not causing the problem here).
After some initial setup I try to remove this view with removeFromSuperview() call but it doesn't get removed.
I'm dispatching on main because I'm making this removal call in a background thread.
Here's the related code:
Adding the view to keyWindow
fileprivate func addOverlapView() {
let frame = CGRect(x: 0, y: 0, width: self.view.frame.width, height: self.view.frame.height)
self.overlapView = UIView(frame: frame)
let overlapGradient: CAGradientLayer = CAGradientLayer()
overlapGradient.colors = [self.fromColor.cgColor, self.toColor.cgColor]
overlapGradient.locations = [0.0, 1.0]
overlapGradient.frame = self.view.bounds
overlapGradient.masksToBounds = true
overlapView.layer.insertSublayer(overlapGradient, at: 0)
let textLabel = UILabel()
textLabel.text = "Configuring user settings"
textLabel.textAlignment = .center
textLabel.textColor = .white
overlapView.addSubview(textLabel)
textLabel.anchor(top: nil, left: nil, bottom: nil, right: nil, paddingTop: 0, paddinfLeft: 0, paddingBottom: 0, paddingRight: 0, width: overlapView.frame.width, height: 100)
textLabel.centerXAnchor.constraint(equalTo: overlapView.centerXAnchor).isActive = true
textLabel.centerYAnchor.constraint(equalTo: overlapView.centerYAnchor).isActive = true
UIApplication.shared.keyWindow?.addSubview(overlapView)
}
And then I'm trying to remove it with the following call:
DispatchQueue.main.async {
self.overlapView.removeFromSuperview()
}
But somehow the view doesn't get removed.
Any hint on what may be causing this?