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.
Related
I need to add a target to an image in a tableview, but it doesn't work if I don't add a view to the contentView. After that, my constraints broke.
https://github.com/termyter/stackoverflow-search
beforeenter image description here
afterenter image description here
PostController
import Foundation
import UIKit
protocol AnswerhNetworkDelegate: AnyObject {
func getListModels(noteModels: [AnswerModel])
}
class PostController: UIViewController, UITableViewDelegate, UITableViewDataSource, AnswerhNetworkDelegate{
func getListModels(noteModels: [AnswerModel]) {
listModels += noteModels
table.reloadData()
}
private var listModels: [AnswerModel] = []
private var searchText = UITextField()
private var searchButton = UIButton()
private var table = UITableView()
private let answerNetwork = AnswerNetwork()
var model: PostModel = PostModel.empty
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
table.register(PostCell.self, forCellReuseIdentifier: "PostCell")
table.register(CustomAnswerCell.self, forCellReuseIdentifier: "CustomAnswerCell")
table.delegate = self
table.dataSource = self
table.estimatedRowHeight = 68.0
table.rowHeight = UITableView.automaticDimension
setupUI()
answerNetwork.answerNetworkDelegate = self
//переделать
answerNetwork.fetch(idPost: model.id)
}
override func viewDidAppear(_ animated: Bool) {
table.reloadData()
}
private func setupUI() {
table.translatesAutoresizingMaskIntoConstraints = false
table.separatorStyle = .none
view.addSubview(table)
table.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
table.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
table.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
table.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
listModels.count + 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0 {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "PostCell", for: indexPath) as? PostCell else {
fatalError("не CustomCell")
}
// cell.cellView.image.userInteractionEnabled =
let lpgr = UITapGestureRecognizer(target: self, action: #selector(PostController.handleTapPress(_:)))
cell.cellView.image.isUserInteractionEnabled = true
cell.cellView.image.addGestureRecognizer(lpgr)
cell.selectionStyle = .none
cell.model = model
return cell
} else {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "CustomAnswerCell", for: indexPath) as? CustomAnswerCell else {
fatalError("не CustomCell")
}
cell.selectionStyle = .none
cell.model = listModels[indexPath.row - 1]
return cell
}
}
#objc func handleTapPress(_ sender: Any){
UIApplication.shared.openURL(URL(string: model.link)!)
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
}
}
extension String {
init?(htmlEncodedString: String) {
guard let data = htmlEncodedString.data(using: .utf8) else {
return nil
}
let options: [NSAttributedString.DocumentReadingOptionKey: Any] = [
.documentType: NSAttributedString.DocumentType.html,
.characterEncoding: String.Encoding.utf8.rawValue
]
guard let attributedString = try? NSAttributedString(data: data, options: options, documentAttributes: nil) else {
return nil
}
self.init(attributedString.string)
}
}
PostCell
import Foundation
import UIKit
class PostCell: UITableViewCell {
var cellView = PostView()
private var selectedButton = UIButton(type: .custom)
var model: PostModel? {
get {
cellView.model
}
set {
cellView.model = newValue ?? PostModel.empty
}
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
backgroundColor = .white
setupView()
// print(cellView.frame)
// print(contentView.frame)
// print(self.frame)
}
override func prepareForReuse() {
super.prepareForReuse()
isHidden = false
isSelected = false
isHighlighted = false
self.model = PostModel.empty
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupView() {
cellView.translatesAutoresizingMaskIntoConstraints = false
self.contentView.addSubview(cellView)
// addSubview(cellView)
cellView.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor).isActive = true
cellView.leadingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.leadingAnchor).isActive = true
cellView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
cellView.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor, constant: -5).isActive = true
}
}
PostView is in the PostCell as a cell and is injected with the Model
import Foundation
import UIKit
class PostView: UIView {
private var titleText = UILabel()
private var bodyText = UILabel()
private var nameText = UILabel()
var image = UIImageView()
private var dateText = UILabel()
private var answerCount = UILabel()
var model: PostModel = PostModel.empty {
didSet {
let formatter = DateFormatter()
formatter.dateFormat = "dd.MM.YYY"
titleText.text = model.title
image.load(urlString: model.image)
nameText.text = "Автор:" + model.name
dateText.text = "Дата: " + formatter.string(from: model.date)
answerCount.text = "Кол-во ответов: " + String(model.answer_count)
bodyText.text = model.body
}
}
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = .systemBackground
setupTitleText()
setupImage()
let tapGR = UITapGestureRecognizer(target: self, action: #selector(self.imageTapped))
image.addGestureRecognizer(tapGR)
image.isUserInteractionEnabled = true
setupNameText()
setupDateText()
setupAnswerCount()
setupBodyText()
}
#objc func imageTapped(sender: UITapGestureRecognizer) {
if sender.state == .ended {
UIApplication.shared.openURL(URL(string: model.link)!)
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupTitleText() {
titleText.translatesAutoresizingMaskIntoConstraints = false
titleText.numberOfLines = 0
let maximumLabelSize: CGSize = CGSize(width: 280, height: 9999)
let expectedLabelSize: CGSize = titleText.sizeThatFits(maximumLabelSize)
var newFrame: CGRect = titleText.frame
newFrame.size.height = expectedLabelSize.height
titleText.frame = newFrame
addSubview(titleText)
titleText.topAnchor.constraint(equalTo: topAnchor, constant: 5).isActive = true
titleText.leadingAnchor.constraint( equalTo: leadingAnchor, constant: 10).isActive = true
titleText.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -10).isActive = true
}
private func setupImage() {
image.translatesAutoresizingMaskIntoConstraints = false
image.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
addSubview(image)
image.topAnchor.constraint(equalTo: titleText.bottomAnchor, constant: 5).isActive = true
image.leadingAnchor.constraint( equalTo: leadingAnchor, constant: 10).isActive = true
image.trailingAnchor.constraint(equalTo: leadingAnchor, constant: 60).isActive = true
//image.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -10).isActive = true
}
private func setupNameText() {
nameText.translatesAutoresizingMaskIntoConstraints = false
addSubview(nameText)
nameText.topAnchor.constraint(equalTo: titleText.bottomAnchor, constant: 5).isActive = true
nameText.leadingAnchor.constraint( equalTo: image.trailingAnchor, constant: 10).isActive = true
nameText.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -10).isActive = true
}
private func setupDateText() {
dateText.translatesAutoresizingMaskIntoConstraints = false
addSubview(dateText)
dateText.topAnchor.constraint(equalTo: nameText.bottomAnchor, constant: 5).isActive = true
dateText.leadingAnchor.constraint( equalTo: image.trailingAnchor, constant: 10).isActive = true
dateText.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -10).isActive = true
}
private func setupAnswerCount() {
answerCount.translatesAutoresizingMaskIntoConstraints = false
addSubview(answerCount)
answerCount.topAnchor.constraint(equalTo: dateText.bottomAnchor, constant: 5).isActive = true
answerCount.leadingAnchor.constraint( equalTo: image.trailingAnchor, constant: 10).isActive = true
answerCount.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -10).isActive = true
// answerCount.bottomAnchor.constraint(equalTo: bottomAnchor, constant: 0).isActive = true
}
private func setupBodyText() {
bodyText.translatesAutoresizingMaskIntoConstraints = false
bodyText.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
bodyText.translatesAutoresizingMaskIntoConstraints = false
bodyText.numberOfLines = 0
let maximumLabelSize: CGSize = CGSize(width: 280, height: 9999)
let expectedLabelSize: CGSize = bodyText.sizeThatFits(maximumLabelSize)
// create a frame that is filled with the UILabel frame data
var newFrame: CGRect = bodyText.frame
// resizing the frame to calculated size
newFrame.size.height = expectedLabelSize.height
// put calculated frame into UILabel frame
bodyText.frame = newFrame
addSubview(bodyText)
bodyText.topAnchor.constraint(equalTo: answerCount.bottomAnchor, constant: 5).isActive = true
bodyText.leadingAnchor.constraint( equalTo: leadingAnchor, constant: 5).isActive = true
bodyText.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -5).isActive = true
bodyText.bottomAnchor.constraint(equalTo: bottomAnchor, constant: 0).isActive = true
}
}
on this github, I looked at how he arranges the constraints and made it for himself https://github.com/jeantimex/ios-swift-collapsible-table-section/blob/master/ios-swift-collapsible-table-section/CollapsibleTableViewCell.swift
and redid PostCell
private func setupView() {
let marginGuide = contentView.layoutMarginsGuide
cellView.translatesAutoresizingMaskIntoConstraints = false
self.contentView.addSubview(cellView)
cellView.leadingAnchor.constraint(equalTo: marginGuide.leadingAnchor).isActive = true
cellView.trailingAnchor.constraint(equalTo: marginGuide.trailingAnchor).isActive = true
cellView.bottomAnchor.constraint(equalTo: marginGuide.bottomAnchor, constant: -5).isActive = true
cellView.topAnchor.constraint(equalTo: marginGuide.topAnchor).isActive = true
}
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)
])
}
}
I am trying to make OTP Pin view and created everything and works well except textfield not moving automatically.
class OTPTextField: UITextField {
var previousTextField: UITextField?
var nextTextFiled: UITextField?
override func deleteBackward() {
text = ""
previousTextField?.becomeFirstResponder()
}
}
#IBDesignable public class OTPView : UIView{
var textFieldArray = [OTPTextField]()
#IBInspectable public var numberOfDots : Int = 4{
didSet{
setUpView()
}
}
#IBInspectable public var bottomLineColor : UIColor = .white{
didSet{
setUpView()
}
}
#IBInspectable public var stackViewSpacing : CGFloat = 10{
didSet{
setUpView()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setUpView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setUpView()
}
public override func layoutSubviews() {
super.layoutSubviews()
}
fileprivate func setUpView(){
let stackView = UIStackView()
stackView.axis = .horizontal
stackView.backgroundColor = .clear
stackView.isUserInteractionEnabled = true
stackView.translatesAutoresizingMaskIntoConstraints = false
// stackView.alignment = .fill
stackView.distribution = .fillEqually
stackView.spacing = CGFloat(stackViewSpacing)
self.addSubview(stackView)
NSLayoutConstraint.activate([
stackView.centerYAnchor.constraint(equalTo: self.centerYAnchor, constant: 0),
stackView.centerXAnchor.constraint(equalTo: self.centerXAnchor, constant: 0),
stackView.heightAnchor.constraint(equalTo: self.heightAnchor, multiplier: 1),
stackView.widthAnchor.constraint(equalTo: self.widthAnchor, multiplier: 1)
])
setTextFields(stackView: stackView)
}
private func setTextFields(stackView : UIStackView) {
for i in 0..<numberOfDots {
let field = OTPTextField()
textFieldArray.append(field)
stackView.addArrangedSubview(field)
field.delegate = self
field.backgroundColor = .clear
field.textAlignment = .center
field.keyboardType = .numberPad
field.addBottomBorderView(lineColor: bottomLineColor)
i != 0 ? (field.previousTextField = textFieldArray[i-1]) : ()
i != 0 ? (textFieldArray[i-1].nextTextFiled = textFieldArray[i]) : ()
}
}
}
extension OTPView: UITextFieldDelegate {
public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
guard let field = textField as? OTPTextField else {
return true
}
if !string.isEmpty {
field.text = string
field.resignFirstResponder()
field.nextTextFiled?.becomeFirstResponder()
return true
}
return true
}
}
extension UITextField {
func addBottomBorder(){
let bottomLine = CALayer()
bottomLine.frame = CGRect(x: 0, y: self.frame.size.height - 1, width: self.frame.size.width, height: 1)
bottomLine.backgroundColor = UIColor.white.cgColor
borderStyle = .none
layer.addSublayer(bottomLine)
}
func addBottomBorderView(lineColor : UIColor) {
var bottomBorder = UIView()
//MARK: Setup Bottom-Border
self.translatesAutoresizingMaskIntoConstraints = false
bottomBorder = UIView.init(frame: CGRect(x: 0, y: 0, width: 0, height: 0))
bottomBorder.backgroundColor = lineColor
bottomBorder.translatesAutoresizingMaskIntoConstraints = false
addSubview(bottomBorder)
//Mark: Setup Anchors
bottomBorder.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
bottomBorder.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
bottomBorder.rightAnchor.constraint(equalTo: rightAnchor).isActive = true
bottomBorder.heightAnchor.constraint(equalToConstant: 1).isActive = true // Set Border-Strength
}
}
Yes Need to remove all created previous fileds from array and from your UIStackView
private func setTextFields() {
self.removeFullyAllArrangedSubviews()
textFieldArray.removeAll()
for i in 0..<numberOfOTPdigit {
let field = OTPTextField()
field.keyboardType = .numberPad
field.textColor = textFieldColor
textFieldArray.append(field)
addArrangedSubview(field)
field.delegate = self
field.backgroundColor = .clear
field.textAlignment = .center
field.addBottomBorderView(lineColor: bottomLineColor)
i != 0 ? (field.previousTextField = textFieldArray[i-1]) : ()
i != 0 ? (textFieldArray[i-1].nextTextFiled = textFieldArray[i]) : ()
print("")
}
}
I have view hierarchy like below;
UITableViewCell ->
-> UIView -> UIStackView (axis: vertical, distribution: fill)
-> UIStackView (axis: horizontal, alignment: top, distribution: fillEqually)
-> UIView -> UIStackView(axis:vertical, distribution: fill)
-> TwoLabelView
My problem is that labels don't get more than one line. I read every question in SO and also tried every possibility but none of them worked. On below screenshot, on the top left box, there should be two pair of label but even one of them isn't showing.
My Question is that how can I achieve multiline in the first box (both for left and right)?
If I change top stack views distribution to fillProportionally, labels get multiline but there will be a gap between last element of first box and the box itself
My first top stack views
//This is the Stackview used just below UITableViewCell
private let stackView: UIStackView = {
let s = UIStackView()
s.distribution = .fill
s.axis = .vertical
s.spacing = 10
s.translatesAutoresizingMaskIntoConstraints = false
return s
}()
//This is used to create two horizontal box next to each other
private let myStackView: UIStackView = {
let s = UIStackView()
s.distribution = .fillEqually
s.spacing = 10
s.axis = .horizontal
//s.alignment = .center
s.translatesAutoresizingMaskIntoConstraints = false
return s
}()
UILabel Class:
fileprivate class FixAutoLabel: UILabel {
override func layoutSubviews() {
super.layoutSubviews()
if(self.preferredMaxLayoutWidth != self.bounds.size.width) {
self.preferredMaxLayoutWidth = self.bounds.size.width
}
}
}
#IBDesignable class TwoLabelView: UIView {
var topMargin: CGFloat = 0.0
var verticalSpacing: CGFloat = 3.0
var bottomMargin: CGFloat = 0.0
#IBInspectable var firstLabelText: String = "" { didSet { updateView() } }
#IBInspectable var secondLabelText: String = "" { didSet { updateView() } }
fileprivate var firstLabel: FixAutoLabel!
fileprivate var secondLabel: FixAutoLabel!
override init(frame: CGRect) {
super.init(frame: frame)
setUpView()
}
required public init?(coder: NSCoder) {
super.init(coder:coder)
setUpView()
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
setUpView()
}
func setUpView() {
firstLabel = FixAutoLabel()
firstLabel.font = UIFont.systemFont(ofSize: 18.0, weight: UIFont.Weight.bold)
firstLabel.numberOfLines = 0
firstLabel.lineBreakMode = NSLineBreakMode.byTruncatingTail
secondLabel = FixAutoLabel()
secondLabel.font = UIFont.systemFont(ofSize: 13.0, weight: UIFont.Weight.regular)
secondLabel.numberOfLines = 1
secondLabel.lineBreakMode = NSLineBreakMode.byTruncatingTail
addSubview(firstLabel)
addSubview(secondLabel)
// we're going to set the constraints
firstLabel .translatesAutoresizingMaskIntoConstraints = false
secondLabel.translatesAutoresizingMaskIntoConstraints = false
// pin both labels' left-edges to left-edge of self
firstLabel.leftAnchor.constraint(equalTo: leftAnchor, constant: 0.0).isActive = true
secondLabel.leftAnchor.constraint(equalTo: leftAnchor, constant: 0.0).isActive = true
// pin both labels' right-edges to right-edge of self
firstLabel.rightAnchor.constraint(equalTo: rightAnchor, constant: 0.0).isActive = true
secondLabel.rightAnchor.constraint(equalTo: rightAnchor, constant: 0.0).isActive = true
// pin firstLabel to the top of self + topMargin (padding)
firstLabel.topAnchor.constraint(equalTo: topAnchor, constant: topMargin).isActive = true
// pin top of secondLabel to bottom of firstLabel + verticalSpacing
secondLabel.topAnchor.constraint(equalTo: firstLabel.bottomAnchor, constant: verticalSpacing).isActive = true
// pin bottom of self to bottom of secondLabel + bottomMargin (padding)
bottomAnchor.constraint(equalTo: secondLabel.bottomAnchor, constant: bottomMargin).isActive = true
// call common "refresh" func
updateView()
}
func updateView() {
firstLabel.preferredMaxLayoutWidth = self.bounds.width
secondLabel.preferredMaxLayoutWidth = self.bounds.width
firstLabel.text = firstLabelText
secondLabel.text = secondLabelText
firstLabel.sizeToFit()
secondLabel.sizeToFit()
setNeedsUpdateConstraints()
}
override open var intrinsicContentSize : CGSize {
// just has to have SOME intrinsic content size defined
// this will be overridden by the constraints
return CGSize(width: 1, height: 1)
}
}
UIView -> UIStackView class
class ViewWithStack: UIView {
let verticalStackView: UIStackView = {
let s = UIStackView()
s.distribution = .fillEqually
s.spacing = 10
s.axis = .vertical
s.translatesAutoresizingMaskIntoConstraints = false
return s
}()
override init(frame: CGRect) {
super.init(frame: frame)
self.translatesAutoresizingMaskIntoConstraints = false
self.backgroundColor = UIColor.white
self.layer.cornerRadius = 6.0
self.layer.applySketchShadow(color: UIColor(red:0.56, green:0.56, blue:0.56, alpha:1), alpha: 0.2, x: 0, y: 0, blur: 10, spread: 0)
addSubview(verticalStackView)
let lessThan = verticalStackView.bottomAnchor.constraint(lessThanOrEqualTo: self.bottomAnchor, constant: 0)
lessThan.priority = UILayoutPriority(1000)
lessThan.isActive = true
verticalStackView.leftAnchor.constraint(equalTo: self.leftAnchor,constant: 0).isActive = true
verticalStackView.rightAnchor.constraint(equalTo: self.rightAnchor,constant: 0).isActive = true
verticalStackView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
verticalStackView.layoutMargins = UIEdgeInsets(top: 10, left: 20, bottom: 10, right: 20)
verticalStackView.isLayoutMarginsRelativeArrangement = true
}
convenience init(orientation: NSLayoutConstraint.Axis,labelsArray: [UIView]) {
self.init()
verticalStackView.axis = orientation
for label in labelsArray {
verticalStackView.addArrangedSubview(label)
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Example Controller Class (This is a minimized version of the whole project):
class ViewController: UIViewController, UITableViewDelegate,UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
let viewWithStack = BoxView()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
tableView.delegate = self
tableView.dataSource = self
tableView.register(TableViewCell.self, forCellReuseIdentifier: "myCell")
tableView.rowHeight = UITableView.automaticDimension
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 2
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: TableViewCell = tableView.dequeueReusableCell(withIdentifier: "myCell") as! TableViewCell
if (indexPath.row == 0) {
cell.setup(viewWithStack: self.viewWithStack)
} else {
cell.backgroundColor = UIColor.black
}
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
//return 500
if ( indexPath.row == 0) {
return UITableView.automaticDimension
} else {
return 40
}
}
}
EDIT I created a minimal project then I found that my problem is that my project implements heightForRow function which overrides UITableViewAutomaticDimension so that It gives wrong height for my component. I think I should look how to get height size of the component? because I can't delete heightForRow function which solves my problem.
Example Project Link https://github.com/emreond/tableviewWithStackView/tree/master/tableViewWithStackViewEx
Example project has ambitious layouts when you open view debugger. I think when I fix them, everything should be fine.
Here is a full example that should do what you want (this is what I mean by a minimal reproducible example):
Best way to examine this is to:
create a new project
create a new file, named TestTableViewController.swift
copy and paste the code below into that file (replace the default template code)
add a UITableViewController to the Storyboard
assign its Custom Class to TestTableViewController
embed it in a UINavigationController
set the UINavigationController as Is Initial View Controller
run the app
This is what you should see as the result:
I based the classes on what you had posted (removed unnecessary code, and I am assuming you have the other cells working as desired).
//
// TestTableViewController.swift
//
// Created by Don Mag on 10/21/19.
//
import UIKit
class SideBySideCell: UITableViewCell {
let horizStackView: UIStackView = {
let v = UIStackView()
v.axis = .horizontal
v.alignment = .fill
v.distribution = .fillEqually
v.spacing = 10
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
override func prepareForReuse() {
horizStackView.arrangedSubviews.forEach {
$0.removeFromSuperview()
}
}
func commonInit() -> Void {
contentView.backgroundColor = UIColor(white: 0.8, alpha: 1.0)
contentView.addSubview(horizStackView)
let g = contentView.layoutMarginsGuide
NSLayoutConstraint.activate([
horizStackView.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0),
horizStackView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0),
horizStackView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
horizStackView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
])
}
func addViewWithStack(_ v: ViewWithStack) -> Void {
horizStackView.addArrangedSubview(v)
}
}
class TestTableViewController: UITableViewController {
let sideBySideReuseID = "sbsID"
override func viewDidLoad() {
super.viewDidLoad()
// register custom SideBySide cell for reuse
tableView.register(SideBySideCell.self, forCellReuseIdentifier: sideBySideReuseID)
tableView.separatorStyle = .none
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: sideBySideReuseID, for: indexPath) as! SideBySideCell
let twoLabelView1 = TwoLabelView()
twoLabelView1.firstLabelText = "Text for first label on left-side."
twoLabelView1.secondLabelText = "10.765,00TL"
let twoLabelView2 = TwoLabelView()
twoLabelView2.firstLabelText = "Text for second-first label on left-side."
twoLabelView2.secondLabelText = "10.765,00TL"
let twoLabelView3 = TwoLabelView()
twoLabelView3.firstLabelText = "Text for the first label on right-side."
twoLabelView3.secondLabelText = "10.765,00TL"
let leftStackV = ViewWithStack(orientation: .vertical, labelsArray: [twoLabelView1, twoLabelView2])
let rightStackV = ViewWithStack(orientation: .vertical, labelsArray: [twoLabelView3])
cell.addViewWithStack(leftStackV)
cell.addViewWithStack(rightStackV)
return cell
}
// create ViewWithStack using just a simple label
let cell = tableView.dequeueReusableCell(withIdentifier: sideBySideReuseID, for: indexPath) as! SideBySideCell
let v = UILabel()
v.text = "This is row \(indexPath.row)"
let aStackV = ViewWithStack(orientation: .vertical, labelsArray: [v])
cell.addViewWithStack(aStackV)
return cell
}
}
#IBDesignable class TwoLabelView: UIView {
var topMargin: CGFloat = 0.0
var verticalSpacing: CGFloat = 3.0
var bottomMargin: CGFloat = 0.0
#IBInspectable var firstLabelText: String = "" { didSet { updateView() } }
#IBInspectable var secondLabelText: String = "" { didSet { updateView() } }
fileprivate var firstLabel: UILabel = {
let v = UILabel()
return v
}()
fileprivate var secondLabel: UILabel = {
let v = UILabel()
return v
}()
override init(frame: CGRect) {
super.init(frame: frame)
setUpView()
}
required public init?(coder: NSCoder) {
super.init(coder:coder)
setUpView()
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
setUpView()
}
func setUpView() {
firstLabel.font = UIFont.systemFont(ofSize: 18.0, weight: UIFont.Weight.bold)
firstLabel.numberOfLines = 0
secondLabel.font = UIFont.systemFont(ofSize: 13.0, weight: UIFont.Weight.regular)
secondLabel.numberOfLines = 1
addSubview(firstLabel)
addSubview(secondLabel)
// we're going to set the constraints
firstLabel .translatesAutoresizingMaskIntoConstraints = false
secondLabel.translatesAutoresizingMaskIntoConstraints = false
// Note: recommended to use Leading / Trailing rather than Left / Right
NSLayoutConstraint.activate([
// pin both labels' left-edges to left-edge of self
firstLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 0.0),
secondLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 0.0),
// pin both labels' right-edges to right-edge of self
firstLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0.0),
secondLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0.0),
// pin firstLabel to the top of self + topMargin (padding)
firstLabel.topAnchor.constraint(equalTo: topAnchor, constant: topMargin),
// pin top of secondLabel to bottom of firstLabel + verticalSpacing
secondLabel.topAnchor.constraint(equalTo: firstLabel.bottomAnchor, constant: verticalSpacing),
// pin bottom of self to >= (bottom of secondLabel + bottomMargin (padding))
bottomAnchor.constraint(greaterThanOrEqualTo: secondLabel.bottomAnchor, constant: bottomMargin),
])
}
func updateView() -> Void {
firstLabel.text = firstLabelText
secondLabel.text = secondLabelText
}
}
class ViewWithStack: UIView {
let verticalStackView: UIStackView = {
let s = UIStackView()
s.distribution = .fill
s.spacing = 10
s.axis = .vertical
s.translatesAutoresizingMaskIntoConstraints = false
return s
}()
override init(frame: CGRect) {
super.init(frame: frame)
self.translatesAutoresizingMaskIntoConstraints = false
self.backgroundColor = UIColor.white
self.layer.cornerRadius = 6.0
// self.layer.applySketchShadow(color: UIColor(red:0.56, green:0.56, blue:0.56, alpha:1), alpha: 0.2, x: 0, y: 0, blur: 10, spread: 0)
addSubview(verticalStackView)
NSLayoutConstraint.activate([
// constrain to all 4 sides
verticalStackView.topAnchor.constraint(equalTo: topAnchor, constant: 0.0),
verticalStackView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: 0.0),
verticalStackView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 0.0),
verticalStackView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0.0),
])
verticalStackView.layoutMargins = UIEdgeInsets(top: 10, left: 20, bottom: 10, right: 20)
verticalStackView.isLayoutMarginsRelativeArrangement = true
}
convenience init(orientation: NSLayoutConstraint.Axis, labelsArray: [UIView]) {
self.init()
verticalStackView.axis = orientation
for label in labelsArray {
verticalStackView.addArrangedSubview(label)
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
I want customized UITextfield which has placeholder behavior same as android also I want error label within UITextfield.
Recently I have seen in android. They have default text area where placeholder does not get hidden when a user starts writing, Instead of hiding they keep placeholder upside of textarea.
Recently i have created my CustomTextfield class which is same as your requirements.
Here is the Github link to download class and its usage https://github.com/indrajitv/CustomTextField
Below is the code,
//
// File.swift
// customTextField
//
// Created by indrajit on 17/08/18.
// Copyright © 2018 indrajit. All rights reserved.
//
import UIKit
#IBDesignable
open class CustomTextField:UITextField{
private var labelPlaceholderTitleTop:NSLayoutConstraint!
private var labelPlaceholderTitleCenterY:NSLayoutConstraint!
private var labelPlaceholderTitleLeft:NSLayoutConstraint!
#IBInspectable var allowToShrinkPlaceholderSizeOnEditing = true
#IBInspectable var shrinkSizeOfPlaceholder:CGFloat = 0
#IBInspectable var placeHolderColor:UIColor = .lightGray{
didSet{
labelPlaceholderTitle.textColor = placeHolderColor
}
}
open override var font: UIFont?{
didSet{
labelPlaceholderTitle.font = font
}
}
#IBInspectable var heightOfBottomLine:CGFloat = 1{
didSet{
heightAnchorOfBottomLine.constant = heightOfBottomLine
}
}
open override var leftView: UIView?{
didSet{
if let lv = leftView{
labelPlaceholderTitleLeft.constant = lv.frame.width+leftPadding
}
}
}
#IBInspectable var leftPadding:CGFloat = 0{
didSet{
labelPlaceholderTitleLeft.constant = leftPadding
}
}
#IBInspectable var errorText:String = ""{
didSet{
self.labelError.text = errorText
}
}
#IBInspectable var errorColor:UIColor = .red{
didSet{
labelError.textColor = errorColor
}
}
#IBInspectable var errorFont:UIFont = UIFont.systemFont(ofSize: 10){
didSet{
self.labelError.font = errorFont
}
}
#IBInspectable var shakeIntensity:CGFloat = 5
private var heightAnchorOfBottomLine:NSLayoutConstraint!
lazy var labelPlaceholderTitle:UILabel={
let label = UILabel()
label.numberOfLines = 0
label.translatesAutoresizingMaskIntoConstraints = false
label.font = self.font
label.adjustsFontSizeToFitWidth = true
return label
}()
lazy var labelError:UILabel={
let label = UILabel()
label.numberOfLines = 0
label.translatesAutoresizingMaskIntoConstraints = false
label.adjustsFontSizeToFitWidth = true
label.text = self.errorText
label.textAlignment = .right
label.font = self.errorFont
label.textColor = errorColor
return label
}()
let bottonLineView:UIView={
let view = UIView()
view.backgroundColor = .red
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
override init(frame: CGRect) {
super.init(frame: frame)
self.initalSetup()
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.initalSetup()
}
override open func prepareForInterfaceBuilder() {
self.initalSetup()
}
open override func awakeFromNib() {
self.labelError.isHidden = true
}
func initalSetup(){
self.labelPlaceholderTitle.text = placeholder
placeholder = nil
borderStyle = .none
bottonLineView.removeFromSuperview()
addSubview(bottonLineView)
bottonLineView.leftAnchor.constraint(equalTo: leftAnchor, constant: 0).isActive = true
bottonLineView.rightAnchor.constraint(equalTo: rightAnchor, constant: 0).isActive = true
bottonLineView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: 0).isActive = true
heightAnchorOfBottomLine = bottonLineView.heightAnchor.constraint(equalToConstant: heightOfBottomLine)
heightAnchorOfBottomLine.isActive = true
addSubview(labelPlaceholderTitle)
labelPlaceholderTitleLeft = labelPlaceholderTitle.leftAnchor.constraint(equalTo: leftAnchor, constant: leftPadding)
labelPlaceholderTitleLeft.isActive = true
labelPlaceholderTitle.rightAnchor.constraint(equalTo: rightAnchor, constant: 0).isActive = true
labelPlaceholderTitleTop = labelPlaceholderTitle.topAnchor.constraint(equalTo: topAnchor, constant: 0)
labelPlaceholderTitleTop.isActive = false
labelPlaceholderTitleCenterY = labelPlaceholderTitle.centerYAnchor.constraint(equalTo: centerYAnchor, constant: 0)
labelPlaceholderTitleCenterY.isActive = true
addSubview(labelError)
labelError.leftAnchor.constraint(equalTo: leftAnchor, constant: 0).isActive = true
labelError.rightAnchor.constraint(equalTo: rightAnchor, constant: 0).isActive = true
labelError.topAnchor.constraint(equalTo: bottonLineView.bottomAnchor, constant: 2).isActive = true
addTarget(self, action: #selector(self.textFieldDidChange), for: .editingChanged)
}
#objc func textFieldDidChange(){
func animateLabel(){
UIView.animate(withDuration: 0.2, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
self.layoutIfNeeded()
}, completion: nil)
}
if let enteredText = text,enteredText != ""{
if labelPlaceholderTitleCenterY.isActive{
labelPlaceholderTitleCenterY.isActive = false
labelPlaceholderTitleTop.isActive = true
labelPlaceholderTitleTop.constant = -5
if allowToShrinkPlaceholderSizeOnEditing{
let currentFont = font == nil ? UIFont.systemFont(ofSize: 16) : font!
let shrinkSize = shrinkSizeOfPlaceholder == 0 ? currentFont.pointSize-3 : shrinkSizeOfPlaceholder
labelPlaceholderTitle.font = UIFont.init(descriptor: currentFont.fontDescriptor, size:shrinkSize)
}
animateLabel()
}
}else{
labelPlaceholderTitleCenterY.isActive = true
labelPlaceholderTitleTop.isActive = false
labelPlaceholderTitleTop.constant = 0
labelPlaceholderTitle.font = font
animateLabel()
}
}
#objc public func showError(){
self.labelError.isHidden = false
let animation = CABasicAnimation(keyPath: "position")
animation.duration = 0.07
animation.repeatCount = 4
animation.autoreverses = true
animation.fromValue = NSValue(cgPoint: CGPoint(x: center.x - shakeIntensity, y: center.y))
animation.toValue = NSValue(cgPoint: CGPoint(x: center.x + shakeIntensity, y: center.y))
layer.add(animation, forKey: "position")
}
#objc public func hideError(){
self.labelError.isHidden = true
}
}
**Usage :
//
// ViewController.swift
// customTextField
//
// Created by indrajit on 16/08/18.
// Copyright © 2018 indrajit. All rights reserved.
//
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var text: CustomTextField!
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func okClicked(_ sender: UIButton) {
if text.text! == ""{
text.showError()
}else{
text.hideError()
}
}
}
Output: