Hi,
I have collection view cell and it appears that some portion of label is clipped. Here is my code:
class FeedbackTagCell: BaseCollectionCell {
override func prepare() {
super.prepare()
setup()
setConstraints()
}
private let lbl: UILabel = {
let lbl = UILabel()
lbl.translatesAutoresizingMaskIntoConstraints = false
lbl.numberOfLines = 1
return lbl
}()
private let icon: UIImageView = {
let imgView = UIImageView()
imgView.translatesAutoresizingMaskIntoConstraints = false
imgView.contentMode = .scaleAspectFit
return imgView
}()
private func setup() {
contentView.layer.cornerRadius = 4.0
contentView.addSubview(lbl)
contentView.addSubview(icon)
}
private func setConstraints() {
let iconLeftOffset: CGFloat = 8
let iconRightOffset: CGFloat = 15
let lblVerticalOffset: CGFloat = 8
let lblLeftOffset: CGFloat = 16
let iconHeightWidth: CGFloat = 10
NSLayoutConstraint.activate([
lbl.leftAnchor.constraint(equalTo: leftAnchor, constant: lblLeftOffset),
lbl.rightAnchor.constraint(equalTo: icon.leftAnchor, constant: -iconLeftOffset),
lbl.topAnchor.constraint(equalTo: contentView.topAnchor, constant: lblVerticalOffset),
lbl.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -lblVerticalOffset),
icon.rightAnchor.constraint(equalTo: rightAnchor, constant: -iconRightOffset),
icon.centerYAnchor.constraint(equalTo: centerYAnchor),
icon.heightAnchor.constraint(equalToConstant: iconHeightWidth),
icon.widthAnchor.constraint(equalToConstant: iconHeightWidth)
])
}
}
This is my code:
import UIKit
class DocumentViewController: UIViewController, UIScrollViewDelegate {
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return scrollViewContainer
}
func scrollViewDidZoom(_ scrollView: UIScrollView) {
let scrollViewSize = scrollView.bounds.size
let scrollViewContainerSize = scrollViewContainer.frame.size
let verticalPadding = scrollViewContainerSize.height < scrollViewSize.height ? (scrollViewSize.height - scrollViewContainerSize.height) / 2 : 0
let horizontalPadding = scrollViewContainerSize.width < scrollViewSize.width ? (scrollViewSize.width - scrollViewContainerSize.width) / 2 : 0
scrollView.contentInset = UIEdgeInsets(top: verticalPadding, left: horizontalPadding, bottom: verticalPadding, right: horizontalPadding)
}
let scrollView: UIScrollView = {
let scrollView = UIScrollView()
scrollView.translatesAutoresizingMaskIntoConstraints = false
scrollView.minimumZoomScale = 0.1
scrollView.maximumZoomScale = 4.0
scrollView.zoomScale = 1.0
return scrollView
}()
let scrollViewContainer: UIStackView = {
let view = UIStackView()
view.translatesAutoresizingMaskIntoConstraints = false
view.axis = .vertical
view.spacing = 15
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
scrollView.delegate = self
view.backgroundColor = .gray
view.addSubview(scrollView)
scrollView.addSubview(scrollViewContainer)
let uiview1 = UIView(frame: .zero)
uiview1.heightAnchor.constraint(equalToConstant: 1000).isActive = true
uiview1.widthAnchor.constraint(equalToConstant: 1000).isActive = true
scrollViewContainer.addArrangedSubview(uiview1)
uiview1.backgroundColor = .white
let uiview2 = UIView(frame: .zero)
uiview2.heightAnchor.constraint(equalToConstant: 1000).isActive = true
uiview2.widthAnchor.constraint(equalToConstant: 1000).isActive = true
scrollViewContainer.addArrangedSubview(uiview2)
uiview2.backgroundColor = .white
let uiview3 = UIView(frame: .zero)
uiview3.heightAnchor.constraint(equalToConstant: 1000).isActive = true
uiview3.widthAnchor.constraint(equalToConstant: 1000).isActive = true
scrollViewContainer.addArrangedSubview(uiview3)
uiview3.backgroundColor = .white
let uiview4 = UIView(frame: .zero)
uiview4.heightAnchor.constraint(equalToConstant: 1000).isActive = true
uiview4.widthAnchor.constraint(equalToConstant: 1000).isActive = true
scrollViewContainer.addArrangedSubview(uiview4)
uiview4.backgroundColor = .white
let uiview5 = UIView(frame: .zero)
uiview5.heightAnchor.constraint(equalToConstant: 1000).isActive = true
uiview5.widthAnchor.constraint(equalToConstant: 2000).isActive = true
scrollViewContainer.addArrangedSubview(uiview5)
uiview5.backgroundColor = .white
showBounds()
setConstraints()
}
func setConstraints() {
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
scrollView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
scrollViewContainer.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor).isActive = true
scrollViewContainer.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor).isActive = true
scrollViewContainer.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
scrollViewContainer.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
}
func showBounds() {
view.layer.borderWidth = 5.0
view.layer.borderColor = UIColor.blue.cgColor
scrollViewContainer.layer.borderWidth = 5.0
scrollViewContainer.layer.borderColor = UIColor.red.cgColor
scrollView.layer.borderWidth = 3.0
scrollView.layer.borderColor = UIColor.yellow.cgColor
}
}
This code gives the following behaviour:
https://i.stack.imgur.com/gNxnD.gif
You can see that whereas the last test UIView I made in viewDidLoad has a width of 2000, it ends up being cut off for some reason, as if its width is 1000. Additionally, if I change the width of the last view to 500, I get this behaviour.
So it seems that scrollViewContainer (?) is resizing to the smallest width out of those test UIViews. All I want is for it to allow different sized views, with the default width being the widest child view, as in this mockup.
You need to add 'alignment' to your stack view. Please try this:
let scrollViewContainer: UIStackView = {
let view = UIStackView()
view.translatesAutoresizingMaskIntoConstraints = false
view.axis = .vertical
view.alignment = .center
view.spacing = 15
return view
}()
I have buttons inside a ButtonView class, to add some background and a label. These ButtonViews are added to a UIStackView which is a view in the PlayOverlay Class. PlayOverlay serves as a parent class to different kinds of overlays, in this example I have only included the BeginOverlay.
BeginOverlay is presented by the PlaySecVC. The Buttons in the BeginOverlay can't be tapped for some reason. I have tried the UIDebugging in XCode to see if there are any views in front of them, and there aren't. They are the frontmost views. I do get one error When UIDebugging that tells me that ButtonView's width, height, and x and y are ambiguous. This is because i have no constraints on it, as shown below, since they are laid out the stack view. How can I make these buttons tappable?
ViewController:
import UIKit
fileprivate struct scvc {
static let overlayWidth: CGFloat = 330
static let overlayFromCenter: CGFloat = 25
static let hotspotSize: CGFloat = 30
static let detailHeight: CGFloat = 214
static let detailWidth: CGFloat = 500
static let arrowMargin: CGFloat = 9
static let arrowSize: CGFloat = 56
static let zoomRect: CGFloat = 200
static let overlayHeight: CGFloat = 267
}
enum playState {
case play
case shuffle
case favorites
}
protocol PlaySec: class {
}
class PlaySecVC: UIViewController, PlaySec {
// MARK: UIComponents
lazy var scrollView: UIScrollView = {
let _scrollView = UIScrollView(frame: .zero)
_scrollView.translatesAutoresizingMaskIntoConstraints = false
_scrollView.clipsToBounds = false
//_scrollView.isUserInteractionEnabled = true
return _scrollView
}()
lazy var imageView: UIImageView = {
let _imageView = UIImageView(frame: .zero)
_imageView.translatesAutoresizingMaskIntoConstraints = false
_imageView.contentMode = .scaleAspectFit
//_imageView.isUserInteractionEnabled = true
return _imageView
}()
lazy var beginOverlay: BeginOverlay = {
let _beginOverlay = BeginOverlay(frame: .zero)
_beginOverlay.translatesAutoresizingMaskIntoConstraints = false
return _beginOverlay
}()
lazy var detailView: UIView = {
let _detailView = UIView(frame: .zero)
_detailView.translatesAutoresizingMaskIntoConstraints = false
_detailView.isHidden = true
//_detailView.isUserInteractionEnabled = false
return _detailView
}()
lazy var leftArrow: UIButton = {
let _leftArrow = UIButton(frame: .zero)
_leftArrow.translatesAutoresizingMaskIntoConstraints = false
_leftArrow.isHidden = false
_leftArrow.setImage(#imageLiteral(resourceName: "Left-Arrow-Outline"), for: .normal)
return _leftArrow
}()
lazy var rightArrow: UIButton = {
let _rightArrow = UIButton(frame: .zero)
_rightArrow.translatesAutoresizingMaskIntoConstraints = false
_rightArrow.isHidden = false
_rightArrow.setImage(#imageLiteral(resourceName: "Right-Arrow-Outline"), for: .normal)
return _rightArrow
}()
var state: playState = .play
// MARK: Setup
private func setup() {
let viewController = self
}
private func setupConstraints() {
view.addSubview(scrollView)
scrollView.addSubview(imageView)
view.addSubview(detailView)
view.addSubview(beginOverlay)
view.addSubview(leftArrow)
view.addSubview(rightArrow)
NSLayoutConstraint.activate([
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
scrollView.topAnchor.constraint(equalTo: view.topAnchor),
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
imageView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
imageView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
imageView.topAnchor.constraint(equalTo: scrollView.topAnchor),
imageView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
beginOverlay.centerXAnchor.constraint(equalTo: view.centerXAnchor),
beginOverlay.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: -25),
beginOverlay.widthAnchor.constraint(equalToConstant: scvc.overlayWidth),
beginOverlay.heightAnchor.constraint(equalToConstant: scvc.overlayHeight),
detailView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
detailView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
detailView.heightAnchor.constraint(equalToConstant: scvc.detailHeight),
detailView.widthAnchor.constraint(equalToConstant: scvc.detailWidth),
leftArrow.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: scvc.arrowMargin),
leftArrow.centerYAnchor.constraint(equalTo: view.centerYAnchor),
leftArrow.widthAnchor.constraint(equalToConstant: scvc.arrowSize),
leftArrow.heightAnchor.constraint(equalToConstant: scvc.arrowSize),
rightArrow.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -1 * scvc.arrowMargin),
rightArrow.centerYAnchor.constraint(equalTo: view.centerYAnchor),
rightArrow.widthAnchor.constraint(equalToConstant: scvc.arrowSize),
rightArrow.heightAnchor.constraint(equalToConstant: scvc.arrowSize),
])
}
func favorite() {
}
func play() {
state = .play
}
func favoritesPlay() {
play()
state = .favorites
}
func shufflePlay() {
play()
state = .shuffle
}
override func viewDidLoad() {
super.viewDidLoad()
setup()
setupConstraints()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
/*var touch: UITouch? = touches.first
if (touch?.view != detailView && !detailView.isHidden) {
detailView.isHidden = true
}*/
super.touchesBegan(touches, with: event)
}
}
Overlay:
fileprivate struct sizeConstants {
static let pillHeight: CGFloat = 38
static let pillCornerRadius: CGFloat = sizeConstants.pillHeight / 2
static let titleFontSize: CGFloat = 13
static let detailFontSize: CGFloat = 10
static let imageCenterToLeading: CGFloat = 3
static let circleDiameter: CGFloat = 66
static let circleRadius: CGFloat = sizeConstants.circleDiameter / 2
static let buttonTextHPadding: CGFloat = 4
static let buttonTextVPadding: CGFloat = 2
static let badgeSpacing: CGFloat = 5.5
static let titleBadgeSpacing: CGFloat = 19
static let badgeImageSize: CGFloat = 32
static let badgeTextFromCenter: CGFloat = 0
static let badgeTextToImage: CGFloat = 8
static let buttonBackgroundToText: CGFloat = 6
static let circleButtonSize: CGFloat = 48
static let rectButtonWidth: CGFloat = 36
static let rectButtonHeight: CGFloat = 39
static let badgesToButtons: CGFloat = 21.5
}
class ButtonView: UIView {
lazy var buttonBackgroundView: UIView = {
let _buttonBackgroundView = UIView(frame: .zero)
_buttonBackgroundView.translatesAutoresizingMaskIntoConstraints = false
_buttonBackgroundView.backgroundColor = .black
_buttonBackgroundView.layer.cornerRadius = sizeConstants.circleRadius
return _buttonBackgroundView
}()
lazy var textBackgroundView: UIView = {
let _textBackgroundView = UIView(frame: .zero)
_textBackgroundView.translatesAutoresizingMaskIntoConstraints = false
_textBackgroundView.backgroundColor = .black
_textBackgroundView.layer.cornerRadius = _textBackgroundView.frame.height / 2
return _textBackgroundView
}()
lazy var button: UIButton = {
let _button = UIButton(frame: .zero)
_button.translatesAutoresizingMaskIntoConstraints = false
return _button
}()
lazy var label: UILabel = {
let _label = UILabel(frame: .zero)
_label.translatesAutoresizingMaskIntoConstraints = false
_label.font = .systemFont(ofSize: 15)
_label.textColor = .white
return _label
}()
var isRect: Bool = false
convenience init(rect: Bool) {
self.init(frame: .zero)
self.isRect = rect
setupViews()
}
override func updateConstraints() {
NSLayoutConstraint.activate([
buttonBackgroundView.topAnchor.constraint(equalTo: topAnchor),
buttonBackgroundView.centerXAnchor.constraint(equalTo: centerXAnchor),
buttonBackgroundView.widthAnchor.constraint(equalToConstant: sizeConstants.circleDiameter),
buttonBackgroundView.heightAnchor.constraint(equalToConstant: sizeConstants.circleDiameter),
button.centerXAnchor.constraint(equalTo: buttonBackgroundView.centerXAnchor),
button.centerYAnchor.constraint(equalTo: buttonBackgroundView.centerYAnchor),
textBackgroundView.topAnchor.constraint(equalTo: buttonBackgroundView.bottomAnchor, constant: sizeConstants.buttonBackgroundToText),
textBackgroundView.centerXAnchor.constraint(equalTo: centerXAnchor),
textBackgroundView.heightAnchor.constraint(equalTo: label.heightAnchor, constant: sizeConstants.buttonTextVPadding),
textBackgroundView.widthAnchor.constraint(equalTo: label.widthAnchor, constant: sizeConstants.buttonTextHPadding),
label.centerXAnchor.constraint(equalTo: centerXAnchor),
label.centerYAnchor.constraint(equalTo: textBackgroundView.centerYAnchor),
])
if (isRect) {
NSLayoutConstraint.activate([
button.widthAnchor.constraint(equalToConstant: sizeConstants.rectButtonWidth),
button.heightAnchor.constraint(equalToConstant: sizeConstants.rectButtonHeight),
])
} else {
NSLayoutConstraint.activate([
button.widthAnchor.constraint(equalToConstant: sizeConstants.circleButtonSize),
button.heightAnchor.constraint(equalToConstant: sizeConstants.circleButtonSize),
])
}
super.updateConstraints()
}
private func setupViews() {
addSubview(buttonBackgroundView)
addSubview(textBackgroundView)
addSubview(label)
addSubview(button)
label.sizeToFit()
setNeedsUpdateConstraints()
}
func setButtonProps(image: UIImage, text: String, target: Any, selector: Selector) {
self.button.addTarget(target, action: selector, for: .touchUpInside)
self.button.setImage(image, for: .normal)
self.label.text = text
}
#objc private func tapped() {
print("tapped")
}
}
class PlayOverlay: UIView {
override init(frame: CGRect) {
super.init(frame: .zero)
}
lazy var badgeStackView: UIStackView = {
let _badgeStackView = UIStackView(frame: .zero)
_badgeStackView.translatesAutoresizingMaskIntoConstraints = false
_badgeStackView.axis = .vertical
_badgeStackView.spacing = sizeConstants.badgeSpacing
_badgeStackView.distribution = .equalSpacing
return _badgeStackView
}()
lazy var buttonStackView: UIStackView = {
let _buttonStackView = UIStackView(frame: .zero)
_buttonStackView.translatesAutoresizingMaskIntoConstraints = false
_buttonStackView.axis = .horizontal
_buttonStackView.distribution = .equalSpacing
return _buttonStackView
}()
var vc: PlaySecVC!
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func updateConstraints() {
NSLayoutConstraint.activate([
badgeStackView.topAnchor.constraint(equalTo: topAnchor, constant: sizeConstants.titleBadgeSpacing),
badgeStackView.centerXAnchor.constraint(equalTo: centerXAnchor),
badgeStackView.widthAnchor.constraint(equalTo: widthAnchor),
buttonStackView.topAnchor.constraint(equalTo: badgeStackView.bottomAnchor, constant: sizeConstants.badgesToButtons),
buttonStackView.widthAnchor.constraint(equalTo: widthAnchor),
buttonStackView.centerXAnchor.constraint(equalTo: centerXAnchor),
buttonStackView.bottomAnchor.constraint(equalTo: bottomAnchor),
])
super.updateConstraints()
}
}
class BeginOverlay: PlayOverlay {
override init(frame: CGRect) {
super.init(frame: .zero)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupViews() {
addSubview(badgeStackView)
addSubview(buttonStackView)
let shuffleButton = ButtonView(rect: false)
shuffleButton.setButtonProps(image: UIImage()/* replaced with empty image for demo */, text: "SHUFFLE", target: self, selector: #selector(shuffle))
let favoritesButton = ButtonView(rect: false)
favoritesButton.setButtonProps(image: UIImage()/* replaced with empty image for demo */, text: "FAVORITES", target: self, selector: #selector(favorites))
let playButton = ButtonView(rect: false)
playButton.setButtonProps(image: UIImage()/* replaced with empty image for demo */, text: "PLAY", target: self, selector: #selector(play))
buttonStackView.addArrangedSubview(shuffleButton)
buttonStackView.addArrangedSubview(favoritesButton)
buttonStackView.addArrangedSubview(playButton)
}
#objc private func shuffle() {
vc.shufflePlay()
}
#objc private func favorites() {
vc.favoritesPlay()
}
#objc private func play() {
vc.play()
}
}
I did some research and I figured out that since there are 2 UIStackView inside BeginOverlay, there is position ambiguity for the second one that contains the 3 UIButton. The image below may help.
Here is a place of fix. Tested with Xcode 11.4 / iOS 13.4
lazy var buttonStackView: UIStackView = {
let _buttonStackView = UIStackView(frame: .zero)
_buttonStackView.translatesAutoresizingMaskIntoConstraints = false
_buttonStackView.axis = .horizontal
_buttonStackView.distribution = .fillEqually // << here !!!
return _buttonStackView
}()
Here is complete tested module (for comparison, just in case I changed anything else). Just created single view iOS project from template and assign controller class in Storyboard.
fileprivate struct scvc {
static let overlayWidth: CGFloat = 330
static let overlayFromCenter: CGFloat = 25
static let hotspotSize: CGFloat = 30
static let detailHeight: CGFloat = 214
static let detailWidth: CGFloat = 500
static let arrowMargin: CGFloat = 9
static let arrowSize: CGFloat = 56
static let zoomRect: CGFloat = 200
static let overlayHeight: CGFloat = 267
}
enum playState {
case play
case shuffle
case favorites
}
protocol PlaySec: class {
}
class PlaySecVC: UIViewController, PlaySec {
// MARK: UIComponents
lazy var scrollView: UIScrollView = {
let _scrollView = UIScrollView(frame: .zero)
_scrollView.translatesAutoresizingMaskIntoConstraints = false
_scrollView.clipsToBounds = false
//_scrollView.isUserInteractionEnabled = true
return _scrollView
}()
lazy var imageView: UIImageView = {
let _imageView = UIImageView(frame: .zero)
_imageView.translatesAutoresizingMaskIntoConstraints = false
_imageView.contentMode = .scaleAspectFit
//_imageView.isUserInteractionEnabled = true
return _imageView
}()
lazy var beginOverlay: BeginOverlay = {
let _beginOverlay = BeginOverlay(frame: .zero)
_beginOverlay.translatesAutoresizingMaskIntoConstraints = false
return _beginOverlay
}()
lazy var detailView: UIView = {
let _detailView = UIView(frame: .zero)
_detailView.translatesAutoresizingMaskIntoConstraints = false
_detailView.isHidden = true
//_detailView.isUserInteractionEnabled = false
return _detailView
}()
lazy var leftArrow: UIButton = {
let _leftArrow = UIButton(frame: .zero)
_leftArrow.translatesAutoresizingMaskIntoConstraints = false
_leftArrow.isHidden = false
_leftArrow.setImage(UIImage(systemName: "arrow.left")!, for: .normal)
return _leftArrow
}()
lazy var rightArrow: UIButton = {
let _rightArrow = UIButton(frame: .zero)
_rightArrow.translatesAutoresizingMaskIntoConstraints = false
_rightArrow.isHidden = false
_rightArrow.setImage(UIImage(systemName: "arrow.right")!, for: .normal)
return _rightArrow
}()
var state: playState = .play
// MARK: Setup
private func setup() {
// let viewController = self
self.beginOverlay.vc = self
}
private func setupConstraints() {
view.addSubview(scrollView)
scrollView.addSubview(imageView)
view.addSubview(detailView)
view.addSubview(beginOverlay)
view.addSubview(leftArrow)
view.addSubview(rightArrow)
NSLayoutConstraint.activate([
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
scrollView.topAnchor.constraint(equalTo: view.topAnchor),
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
imageView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
imageView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
imageView.topAnchor.constraint(equalTo: scrollView.topAnchor),
imageView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
beginOverlay.centerXAnchor.constraint(equalTo: view.centerXAnchor),
beginOverlay.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: -25),
beginOverlay.widthAnchor.constraint(equalToConstant: scvc.overlayWidth),
beginOverlay.heightAnchor.constraint(equalToConstant: scvc.overlayHeight),
detailView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
detailView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
detailView.heightAnchor.constraint(equalToConstant: scvc.detailHeight),
detailView.widthAnchor.constraint(equalToConstant: scvc.detailWidth),
leftArrow.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: scvc.arrowMargin),
leftArrow.centerYAnchor.constraint(equalTo: view.centerYAnchor),
leftArrow.widthAnchor.constraint(equalToConstant: scvc.arrowSize),
leftArrow.heightAnchor.constraint(equalToConstant: scvc.arrowSize),
rightArrow.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -1 * scvc.arrowMargin),
rightArrow.centerYAnchor.constraint(equalTo: view.centerYAnchor),
rightArrow.widthAnchor.constraint(equalToConstant: scvc.arrowSize),
rightArrow.heightAnchor.constraint(equalToConstant: scvc.arrowSize),
])
}
func favorite() {
}
func play() {
state = .play
}
func favoritesPlay() {
play()
state = .favorites
}
func shufflePlay() {
play()
state = .shuffle
}
override func viewDidLoad() {
super.viewDidLoad()
setup()
setupConstraints()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
/*var touch: UITouch? = touches.first
if (touch?.view != detailView && !detailView.isHidden) {
detailView.isHidden = true
}*/
super.touchesBegan(touches, with: event)
}
}
fileprivate struct sizeConstants {
static let pillHeight: CGFloat = 38
static let pillCornerRadius: CGFloat = sizeConstants.pillHeight / 2
static let titleFontSize: CGFloat = 13
static let detailFontSize: CGFloat = 10
static let imageCenterToLeading: CGFloat = 3
static let circleDiameter: CGFloat = 66
static let circleRadius: CGFloat = sizeConstants.circleDiameter / 2
static let buttonTextHPadding: CGFloat = 4
static let buttonTextVPadding: CGFloat = 2
static let badgeSpacing: CGFloat = 5.5
static let titleBadgeSpacing: CGFloat = 19
static let badgeImageSize: CGFloat = 32
static let badgeTextFromCenter: CGFloat = 0
static let badgeTextToImage: CGFloat = 8
static let buttonBackgroundToText: CGFloat = 6
static let circleButtonSize: CGFloat = 48
static let rectButtonWidth: CGFloat = 36
static let rectButtonHeight: CGFloat = 39
static let badgesToButtons: CGFloat = 21.5
}
class ButtonView: UIView {
lazy var buttonBackgroundView: UIView = {
let _buttonBackgroundView = UIView(frame: .zero)
_buttonBackgroundView.translatesAutoresizingMaskIntoConstraints = false
_buttonBackgroundView.backgroundColor = .black
_buttonBackgroundView.layer.cornerRadius = sizeConstants.circleRadius
return _buttonBackgroundView
}()
lazy var textBackgroundView: UIView = {
let _textBackgroundView = UIView(frame: .zero)
_textBackgroundView.translatesAutoresizingMaskIntoConstraints = false
_textBackgroundView.backgroundColor = .black
_textBackgroundView.layer.cornerRadius = _textBackgroundView.frame.height / 2
return _textBackgroundView
}()
lazy var button: UIButton = {
let _button = UIButton(frame: .zero)
_button.translatesAutoresizingMaskIntoConstraints = false
return _button
}()
lazy var label: UILabel = {
let _label = UILabel(frame: .zero)
_label.translatesAutoresizingMaskIntoConstraints = false
_label.font = .systemFont(ofSize: 15)
_label.textColor = .white
return _label
}()
var isRect: Bool = false
convenience init(rect: Bool) {
self.init(frame: .zero)
self.isRect = rect
setupViews()
}
override func updateConstraints() {
NSLayoutConstraint.activate([
buttonBackgroundView.topAnchor.constraint(equalTo: topAnchor),
buttonBackgroundView.centerXAnchor.constraint(equalTo: centerXAnchor),
buttonBackgroundView.widthAnchor.constraint(equalToConstant: sizeConstants.circleDiameter),
buttonBackgroundView.heightAnchor.constraint(equalToConstant: sizeConstants.circleDiameter),
button.centerXAnchor.constraint(equalTo: buttonBackgroundView.centerXAnchor),
button.centerYAnchor.constraint(equalTo: buttonBackgroundView.centerYAnchor),
textBackgroundView.topAnchor.constraint(equalTo: buttonBackgroundView.bottomAnchor, constant: sizeConstants.buttonBackgroundToText),
textBackgroundView.centerXAnchor.constraint(equalTo: centerXAnchor),
textBackgroundView.heightAnchor.constraint(equalTo: label.heightAnchor, constant: sizeConstants.buttonTextVPadding),
textBackgroundView.widthAnchor.constraint(equalTo: label.widthAnchor, constant: sizeConstants.buttonTextHPadding),
label.centerXAnchor.constraint(equalTo: centerXAnchor),
label.centerYAnchor.constraint(equalTo: textBackgroundView.centerYAnchor),
])
if (isRect) {
NSLayoutConstraint.activate([
button.widthAnchor.constraint(equalToConstant: sizeConstants.rectButtonWidth),
button.heightAnchor.constraint(equalToConstant: sizeConstants.rectButtonHeight),
])
} else {
NSLayoutConstraint.activate([
button.widthAnchor.constraint(equalToConstant: sizeConstants.circleButtonSize),
button.heightAnchor.constraint(equalToConstant: sizeConstants.circleButtonSize),
])
}
super.updateConstraints()
}
private func setupViews() {
addSubview(buttonBackgroundView)
addSubview(textBackgroundView)
addSubview(label)
addSubview(button)
label.sizeToFit()
setNeedsUpdateConstraints()
}
func setButtonProps(image: UIImage, text: String, target: Any, selector: Selector) {
self.button.addTarget(target, action: selector, for: .touchUpInside)
self.button.setImage(image, for: .normal)
self.label.text = text
}
#objc private func tapped() {
print("tapped")
}
}
class PlayOverlay: UIView {
override init(frame: CGRect) {
super.init(frame: .zero)
}
lazy var badgeStackView: UIStackView = {
let _badgeStackView = UIStackView(frame: .zero)
_badgeStackView.translatesAutoresizingMaskIntoConstraints = false
_badgeStackView.axis = .vertical
_badgeStackView.spacing = sizeConstants.badgeSpacing
_badgeStackView.distribution = .equalSpacing
return _badgeStackView
}()
lazy var buttonStackView: UIStackView = {
let _buttonStackView = UIStackView(frame: .zero)
_buttonStackView.translatesAutoresizingMaskIntoConstraints = false
_buttonStackView.axis = .horizontal
_buttonStackView.distribution = .fillEqually
return _buttonStackView
}()
var vc: PlaySecVC!
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func updateConstraints() {
NSLayoutConstraint.activate([
badgeStackView.topAnchor.constraint(equalTo: topAnchor, constant: sizeConstants.titleBadgeSpacing),
badgeStackView.centerXAnchor.constraint(equalTo: centerXAnchor),
badgeStackView.widthAnchor.constraint(equalTo: widthAnchor),
buttonStackView.topAnchor.constraint(equalTo: badgeStackView.bottomAnchor, constant: sizeConstants.badgesToButtons),
buttonStackView.widthAnchor.constraint(equalTo: widthAnchor),
buttonStackView.centerXAnchor.constraint(equalTo: centerXAnchor),
buttonStackView.bottomAnchor.constraint(equalTo: bottomAnchor),
])
super.updateConstraints()
}
}
class BeginOverlay: PlayOverlay {
override init(frame: CGRect) {
super.init(frame: .zero)
self.setupViews()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupViews() {
addSubview(badgeStackView)
addSubview(buttonStackView)
let shuffleButton = ButtonView(rect: false)
shuffleButton.setButtonProps(image: UIImage(systemName: "shuffle")!/* replaced with empty image for demo */, text: "SHUFFLE", target: self, selector: #selector(shuffle))
let favoritesButton = ButtonView(rect: false)
favoritesButton.setButtonProps(image: UIImage(systemName: "bookmark")!/* replaced with empty image for demo */, text: "FAVORITES", target: self, selector: #selector(favorites))
let playButton = ButtonView(rect: false)
playButton.setButtonProps(image: UIImage(systemName: "play")!/* replaced with empty image for demo */, text: "PLAY", target: self, selector: #selector(play))
buttonStackView.addArrangedSubview(shuffleButton)
buttonStackView.addArrangedSubview(favoritesButton)
buttonStackView.addArrangedSubview(playButton)
}
#objc private func shuffle() {
vc.shufflePlay()
}
#objc private func favorites() {
vc.favoritesPlay()
}
#objc private func play() {
vc.play()
}
}
Note: as mentioned it is better to review all constrains and fix run-time ambiguities.
Trying to create a UIScrollView and I cant seem to get my labels to appear as I would like them dead center.
lazy var label : UILabel = {
let label = UILabel()
label.text = "center of container view.center of container view"
label.font = UIFont(name: "Bebas Neue", size: 23)!
label.textColor = .black
return label
}()
// Mark: Properties
lazy var contentViewSize = CGSize(width: self.view.frame.width + 1200, height: self.view.frame.height)
// Mark: Views
fileprivate lazy var parentScrollView : UIView = {
var newView = UIView()
newView.translatesAutoresizingMaskIntoConstraints = false
newView.backgroundColor = .black
newView.frame = self.scrollView.bounds
newView.frame.size = contentViewSize
return newView
}()
fileprivate lazy var scrollView : UIScrollView = {
var uiScrollView = UIScrollView(frame: .zero)
uiScrollView.translatesAutoresizingMaskIntoConstraints = false
uiScrollView.frame = view.bounds
uiScrollView.backgroundColor = .gray
uiScrollView.addViewBorder(borderColor: UIColor.gray.cgColor, borderWith: 10, borderCornerRadius: 0)
// uiScrollView.alpha =
uiScrollView.contentSize = contentViewSize
// uiScrollView.autoresizingMask = .flexibleHeight
uiScrollView.showsVerticalScrollIndicator = true
uiScrollView.bounces = true
return uiScrollView
}()
fileprivate lazy var tutorialView : UIStackView = {
var tutView = UIStackView(arrangedSubviews: [label,label,label])
tutView.axis = .horizontal
tutView.backgroundColor = .white
tutView.distribution = .fillEqually
tutView.spacing = 0
return tutView
}()
override func viewDidLoad() {
view.backgroundColor = .white
view.addSubview(scrollView)
scrollView.addSubview(parentScrollView)
parentScrollView.addSubview(tutorialView)
scrollView.widthAnchor.constraint(equalTo:self.view.widthAnchor,multiplier: 0.9).isActive = true
scrollView.heightAnchor.constraint(equalTo:self.view.heightAnchor,multiplier: 0.7).isActive = true
scrollView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
scrollView.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 75).isActive = true
tutorialView.topAnchor.constraint(equalTo: parentScrollView.topAnchor,constant: +10).isActive = true}
It seems that when I add that last constraight with the topAnchor I get a blank screen. When I take that off I actually get the borders between the two views and I am able to do some scrolling.
Any help would be appreciated. Thank you
Did you give a value to the scrollView?
uiScrollView.contentSize.width = 1200
Then give the tutorialView the same width constraint with the other constraints.
NSLayoutConstraint.activate([
tutorialView.widthAnchor.constraint(equalToConstant: 1200),
tutorialView.heightAnchor.constraint(equalTo: uiScrollView.heightAnchor)
])
You have to add tutView.translatesAutoresizingMaskIntoConstraints = false when giving layout constraints in closure of tutorialView and also there are some mistakes in your code. So included some changes in your code and check updated code:
lazy var label : UILabel = {
let label = UILabel()
label.text = "center of container view.center of container view"
label.font = UIFont(name: "Bebas Neue", size: 23)!
label.textColor = .green
return label
}()
// Mark: Properties
lazy var contentViewSize = CGSize(width: self.view.frame.width + 1200, height: self.view.frame.height)
// Mark: Views
fileprivate lazy var parentScrollView : UIView = {
var newView = UIView()
newView.translatesAutoresizingMaskIntoConstraints = false
newView.backgroundColor = UIColor.clear
// newView.layer.borderWidth = 2
// newView.layer.borderColor = UIColor.yellow.cgColor
return newView
}()
fileprivate lazy var scrollView : UIScrollView = {
var uiScrollView = UIScrollView()
uiScrollView.translatesAutoresizingMaskIntoConstraints = false
// uiScrollView.frame = view.bounds
uiScrollView.backgroundColor = .orange
uiScrollView.layer.borderColor = UIColor.blue.cgColor
uiScrollView.layer.borderWidth = 2
uiScrollView.layer.cornerRadius = 0
// uiScrollView.alpha =
uiScrollView.contentSize = contentViewSize
// uiScrollView.autoresizingMask = .flexibleHeight
uiScrollView.showsVerticalScrollIndicator = true
uiScrollView.bounces = true
return uiScrollView
}()
fileprivate lazy var tutorialView : UIStackView = {
var tutView = UIStackView(arrangedSubviews: [label,label,label])
tutView.axis = .horizontal
tutView.backgroundColor = .red
tutView.distribution = .fill
tutView.translatesAutoresizingMaskIntoConstraints = false
return tutView
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
view.addSubview(scrollView)
scrollView.addSubview(parentScrollView)
parentScrollView.addSubview(tutorialView)
scrollView.widthAnchor.constraint(equalTo:self.view.widthAnchor,multiplier: 0.9).isActive = true
scrollView.heightAnchor.constraint(equalTo:self.view.heightAnchor,multiplier: 0.7).isActive = true
scrollView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
scrollView.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor, constant: 25).isActive = true
parentScrollView.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor).isActive = true
parentScrollView.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 10).isActive = true
parentScrollView.heightAnchor.constraint(equalToConstant: 200).isActive = true
parentScrollView.widthAnchor.constraint(equalToConstant: 200).isActive = true
// scrollView.layer.borderWidth = 2
// scrollView.layer.borderColor = UIColor.blue.cgColor
tutorialView.heightAnchor.constraint(equalTo: parentScrollView.heightAnchor).isActive = true
tutorialView.widthAnchor.constraint(equalToConstant: 1200).isActive = true
}
Yes this question has been asked before, the solutions did not work or had different applications.
It is the most basic setup. I have two rectangular UIViews, red and blue.
I would like the blue square to cut into the red square, so the red square looks like an "L"
import Foundation
import UIKit
class TestController: UIViewController {
override func viewDidLoad() {
view.backgroundColor = .gray
view.addSubview(viewA)
view.addSubview(maskView)
viewA.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
viewA.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
viewA.widthAnchor.constraint(equalToConstant: 100).isActive = true
viewA.heightAnchor.constraint(equalToConstant: 100).isActive = true
viewA.translatesAutoresizingMaskIntoConstraints = false
maskView.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 50).isActive = true
maskView.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: -50).isActive = true
maskView.widthAnchor.constraint(equalToConstant: 100).isActive = true
maskView.heightAnchor.constraint(equalToConstant: 100).isActive = true
maskView.translatesAutoresizingMaskIntoConstraints = false
// Things which don't work
//viewA.mask = maskView // both views disappear
//viewA.layer.mask = maskView.layer // both views disappear
//viewA.layer.addSublayer(maskView.layer) // hides mask view
}
var viewA: UIView = {
let view = UIView()
view.backgroundColor = .red
view.layer.masksToBounds = true
return view
}()
var maskView: UIView = {
let view = UIView()
view.backgroundColor = .blue
return view
}()
}
This is the result I am expecting: (done in Photoshop)
As there is no magic way to mask the way in iOS, I present here a simple way to achieve this.
Don't forget to pan the clear area, If leaving the red square, it will become a blue square.
It's not hard to modify the subclass of UIViews for your own purpose, especially views.
import UIKit
class TestController: UIViewController {
override func viewDidLoad() {
view.backgroundColor = .gray
view.addSubview(viewA)
view.addSubview(maskView)
maskView.maskedView = viewA
viewA.activeMask = maskView
viewA.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
viewA.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
viewA.widthAnchor.constraint(equalToConstant: 100).isActive = true
viewA.heightAnchor.constraint(equalToConstant: 100).isActive = true
viewA.translatesAutoresizingMaskIntoConstraints = false
maskView.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 50).isActive = true
maskView.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: -50).isActive = true
maskView.widthAnchor.constraint(equalToConstant: 100).isActive = true
maskView.heightAnchor.constraint(equalToConstant: 100).isActive = true
maskView.translatesAutoresizingMaskIntoConstraints = false
}
var viewA: MyUIView = {
let view = MyUIView()
view.backgroundColor = .clear
view.layer.masksToBounds = true
return view
}()
var maskView: ActiveMaskView = {
let view = ActiveMaskView()
view.backgroundColor = .clear
return view
}()
}
class ActiveMaskView: UIView{
override func didMoveToSuperview() {
super.didMoveToSuperview()
let panGesture = UIPanGestureRecognizer.init(target: self, action: #selector(moveAround(_:)))
self.addGestureRecognizer(panGesture)
}
weak var maskedView : UIView?
private var frameOrigin : CGPoint = CGPoint.zero
#objc func moveAround(_ panGesture: UIPanGestureRecognizer){
guard let superview = superview else {return}
switch panGesture.state {
case .began:
frameOrigin = frame.origin
self.backgroundColor = UIColor.blue
case .changed:
let translation = panGesture.translation(in: superview)
frame = CGRect.init(origin: CGPoint.init(x: frameOrigin.x + translation.x, y: frameOrigin.y + translation.y), size: frame.size)
maskedView?.setNeedsDisplay()
break
case .ended:
self.backgroundColor =
frame.intersects(maskedView!.frame) ?
UIColor.clear : UIColor.blue
maskedView?.setNeedsDisplay()
case .cancelled:
frame = CGRect.init(origin: frameOrigin , size: frame.size)
self.backgroundColor =
frame.intersects(maskedView!.frame) ?
UIColor.clear : UIColor.blue
maskedView?.setNeedsDisplay()
default:
break;
}
}
}
class MyUIView: UIView{
weak var activeMask: ActiveMaskView?
override func draw(_ rect: CGRect) {
super.draw(rect)
let ctx = UIGraphicsGetCurrentContext()
ctx?.setFillColor(UIColor.red.cgColor)
ctx?.fill(self.layer.bounds)
ctx?.setBlendMode(.sourceOut)
guard let activeMask = activeMask , let superview = superview else {
return
}
let sc = frame.intersection(activeMask.frame)
let interSection = superview.convert(sc, to: self)
ctx?.fill(interSection )
}
}