Animate constraints change UIViewController - ios

I have "error" view, that is placed above navigation bar and hidden. When error occured, i want that view to smoothly show from top. I tried:
class AuthViewController: UIViewController {
let error: ErrorView = {
let error = ErrorView()
error.setup()
return error
}()
var topAnchor: NSLayoutConstraint!
var botAnchor: NSLayoutConstraint!
override func viewDidLoad() {
setupErrorView()
}
private func setupErrorView(){
view.addSubview(error)
botAnchor = error.bottomAnchor.constraint(equalTo: view.topAnchor)
botAnchor.isActive = true
topAnchor = error.topAnchor.constraint(equalTo: view.topAnchor, constant: CGFloat(Offsets.navigationAndStatusBarHeight))
topAnchor.isActive = false
error.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
error.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
}
func showError(_ text: String){
UIView.animate(withDuration: 2.0) {[weak self] in
guard let weakSelf = self else { return }
print("attempt to animate")
weakSelf.error.show(text)
weakSelf.botAnchor.isActive = false
weakSelf.topAnchor.isActive = true
weakSelf.view.setNeedsLayout()
}
}
}
class ErrorView: UIView {
private var label: UILabel = {
return LabelSL.regular()
}()
fileprivate func setup(){
translatesAutoresizingMaskIntoConstraints = false
backgroundColor = Theme.Color.orange.value
addSubview(label)
}
fileprivate func show(_ text: String){
let sideOffset: CGFloat = 10
let verticalOffset: CGFloat = 10
label.text = text
label.topAnchor.constraint(equalTo: topAnchor, constant: verticalOffset).isActive = true
label.leftAnchor.constraint(equalTo: leftAnchor, constant: sideOffset).isActive = true
label.rightAnchor.constraint(equalTo: rightAnchor, constant: -sideOffset).isActive = true
label.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -verticalOffset).isActive = true
}
}
Animation should be done when func showError(_ text: String){ method called, but it's not. View just appear instantly.

You're trying to animate constraints in wrong way. You should set constraints outside of animation block and only layoutIfNeeded in animation:
func showError(_ text: String){
botAnchor.isActive = false
topAnchor.isActive = true
error.show(text)
UIView.animate(withDuration: 2.0) {
self.view.layoutIfNeeded()
}
}

Related

trouble with constructs after adding view to contentView

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
}

the view size gets smaller when the view is rotated

I have a view. when it's rotated, the view size gets smaller. would you please take a look at the pics that I've attached?
#IBAction func buttonForNewView(_ sender: Any) {
self.view.addSubview(customvView)
customvView.layer.cornerRadius = 15
customvView.backgroundColor = .gray
customvView.translatesAutoresizingMaskIntoConstraints = false
customvView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: view.frame.size.height*0.1).isActive = true
customvView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: view.frame.size.width*0.1).isActive = true
customvView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -view.frame.size.width*0.1).isActive = true
customvView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant:-view.frame.size.height*0.1).isActive = true
}
The constraints are not recalculated when the device is rotated.
This breaks out the constraint calculation into a separate function. Then it overrides traitCollectionDidChange(). When the trait collection changes, it removes the old constraints (to prevent auto layout from getting confused) and reapplies new constraints.
import UIKit
class ViewController: UIViewController {
var customvView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
customvView = UIView()
// Do any additional setup after loading the view.
}
func constrainCustomvView() {
customvView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: view.frame.size.height*0.1).isActive = true
customvView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: view.frame.size.width*0.1).isActive = true
customvView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -view.frame.size.width*0.1).isActive = true
customvView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant:-view.frame.size.height*0.1).isActive = true
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
if view.subviews.contains(customvView) { // for the case that the traits change before the view is created
// remove the existing constraits, or autolayout will have to choose between two and it will choose poorly
for constraint in view.constraints {
if let first = constraint.firstItem as? UIView, first == customvView {
view.removeConstraint(constraint)
}
if let second = constraint.secondItem as? UIView, second == customvView {
view.removeConstraint(constraint)
}
}
constrainCustomvView()
}
}
#IBAction func CustomButton(_ sender: Any) {
self.view.addSubview(customvView)
customvView.layer.cornerRadius = 15
customvView.backgroundColor = .gray
customvView.translatesAutoresizingMaskIntoConstraints = false
constrainCustomvView()
}
}
check this code.
let val = view.frame.size.height*0.1
self.view.addSubview(customvView)
customvView.layer.cornerRadius = 15
customvView.backgroundColor = .gray
customvView.translatesAutoresizingMaskIntoConstraints = false
customvView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: val).isActive = true
customvView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: val).isActive = true
customvView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -val).isActive = true
customvView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant:-val).isActive = true

Updating layout anchors not working as expected

I have created a view with height 100 using NSLayout anchors. When I'm trying to update that on button click, it's not working.
I have tried below code, but it's not working.
class ViewController: UIViewController {
#IBOutlet weak var button: UIButton!
let viewAnimate = UIView()
var isHidden = false
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(viewAnimate)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
viewAnimate.translatesAutoresizingMaskIntoConstraints = false
viewAnimate.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 8).isActive = true
viewAnimate.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -8).isActive = true
viewAnimate.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 100).isActive = true
viewAnimate.heightAnchor.constraint(equalToConstant: 100).isActive = true
viewAnimate.backgroundColor = UIColor.red
}
#IBAction func show() {
if !isHidden {
viewAnimate.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 200).isActive = true
button.setTitle("Show", for: .normal)
} else {
button.setTitle("Hide", for: .normal)
viewAnimate.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 100).isActive = true
}
UIView.animate(withDuration: 1) {
self.viewAnimate.layoutIfNeeded()
}
isHidden = !isHidden
}
}
View should change the height based on height constraint
Your current code creates conflicts as every line like viewAnimate.topAnchor.constraint(equalTo: adds a new constraint , create a var
var topCon:NSLayoutConstraint!
topCon = viewAnimate.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 100)
topCon.isActive = true
#IBAction func show() {
if !isHidden {
topCon.constant = 200
button.setTitle("Show", for: .normal)
} else {
button.setTitle("Hide", for: .normal)
topCon.constant = 100
}
UIView.animate(withDuration: 1) {
self.view.layoutIfNeeded()
}
isHidden = !isHidden
}

call label postion and size from outside of class (Swift4)

What I am trying to do is assign the position and size of a label from outside a class. Then within 2 separate classes call the label to add text to it. This would save time a lot of time if this would work.
let backbutton = UILabel!
backbutton.translatesAutoresizingMaskIntoConstraints = false
backbutton.leftAnchor.constraint(equalTo: _, constant: 20).isActive = true
backbutton.topAnchor.constraint(equalTo: _, constant: 125).isActive = true
backbutton.widthAnchor.constraint(equalToConstant: 50).isActive = true
backbutton.heightAnchor.constraint(equalToConstant: 50).isActive = true
class nineViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
backbutton.text = String("red")
}
}
class two: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
backbutton.text = String("two")
}
}
Create a Utilities class separately to use the functions that are inside it globally.
Utilities:
class Utilities: NSObject
{
class func createLabel(on view: UIView, horizontalAnchors hAnchors: (leading: CGFloat, leadingView: UIView, trailing: CGFloat, trailingView: UIView), verticalAnchors vAnchors: (top: CGFloat, topView: UIView, bottom: CGFloat, bottomView: UIView)) -> UILabel {
let label = UILabel()
view.addSubview(label)
label.backgroundColor = UIColor.red
label.translatesAutoresizingMaskIntoConstraints = false
label.leadingAnchor.constraint(equalTo: hAnchors.leadingView.leadingAnchor, constant: hAnchors.leading).isActive = true
label.trailingAnchor.constraint(equalTo: hAnchors.trailingView.trailingAnchor, constant: -hAnchors.trailing).isActive = true
label.topAnchor.constraint(equalTo: vAnchors.topView.topAnchor, constant: vAnchors.top).isActive = true
label.bottomAnchor.constraint(equalTo: vAnchors.bottomView.topAnchor, constant: -vAnchors.bottom).isActive = true
return label
}
class func createLabel(on view: UIView, positionAnchors pAnchors: (leading: CGFloat, leadingView: UIView, top: CGFloat, topView: UIView), size: (width: CGFloat, height: CGFloat)) -> UILabel {
let label = UILabel()
view.addSubview(label)
label.backgroundColor = UIColor.red
label.translatesAutoresizingMaskIntoConstraints = false
label.leadingAnchor.constraint(equalTo: pAnchors.leadingView.leadingAnchor, constant: pAnchors.leading).isActive = true
label.topAnchor.constraint(equalTo: pAnchors.topView.topAnchor, constant: pAnchors.top).isActive = true
label.widthAnchor.constraint(equalToConstant: size.width).isActive = true
label.heightAnchor.constraint(equalToConstant: size.height).isActive = true
return label
}
}
In ViewController:
#IBOutlet weak var autoLayedoutLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
let originY: CGFloat = 50
let spacing: CGFloat = 16
let width: CGFloat = 300
let height: CGFloat = 50
let label = Utilities.createLabel(on: view, positionAnchors: (spacing, view, originY, view), size: (width, height))
label.text = "Label with Position Anchors & Size"
label.backgroundColor = UIColor.red
let label2 = Utilities.createLabel(on: view, horizontalAnchors: (spacing, view, spacing, view), verticalAnchors: (spacing + height, label, spacing, autoLayedoutLabel))
label2.text = "Label with Horizontal & Vertical Anchors"
label2.backgroundColor = UIColor.green
}
You can have different variable for buttonText and set his position and size in his setter like
var buttonText:String {
didSet{
backButton.text = buttonText
setFontAndPosition()
}
}
and in viewController just set the value
override func viewDidLoad() {
super.viewDidLoad()
buttonText = "red"
}
I found it's feasible to directly use global UILable. If you don't need to manage too many labels, this is the simplest way.
A TabBarcontroller is used for testing here.
let backbutton = UILabel()
class MyTabBarController : UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
setViewControllers([SettingViewController(), NineViewController(), TwoViewController()], animated: false)
}
}
class SettingViewController: UIViewController {
override var tabBarItem: UITabBarItem!{
get {
return UITabBarItem.init(title: "setting", image: nil, tag: 0)
}
set{
super.tabBarItem = newValue
}
}
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.white
self.view.addSubview(backbutton)
backbutton.text = "cool"
backbutton.translatesAutoresizingMaskIntoConstraints = false
backbutton.leftAnchor.constraint(equalTo: self.view.leftAnchor, constant: 20).isActive = true
backbutton.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 125).isActive = true
backbutton.widthAnchor.constraint(equalToConstant: 50).isActive = true
backbutton.heightAnchor.constraint(equalToConstant: 50).isActive = true
}
}
class NineViewController: UIViewController {
override var tabBarItem: UITabBarItem!{
get {
return UITabBarItem.init(title: "nine", image: nil, tag: 0)
}
set{
super.tabBarItem = newValue
}
}
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.white
backbutton.text = String("red")
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
backbutton.text = String("red-Appear")
}
}
class TwoViewController: UIViewController {
override var tabBarItem: UITabBarItem!{
get {
return UITabBarItem.init(title: "two", image: nil, tag: 0)
}
set{
super.tabBarItem = newValue
}
}
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.white
backbutton.text = String("two")
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
backbutton.text = String("two-Appear")
}
}
If you prefer defining the label inside one class. You may define the global UILabel as this:
weak var backbutton: UILabel!
class SettingViewController: UIViewController {
let mybutton = UILabel()
backbutton = mybutton
// continue
}
You don't need to change any other codes.
Now is the second part of the story. If you wanna setup a global UILabel outside any view, is that possible. Without constraints it's very simple like this:
let backbutton: UILabel! = {
let button = UILabel()
button.text = "test"
button.frame = CGRect.init(x: 200, y: 200, width: 50, height: 50)
return button
}()
The setting View changes like this :
class SettingViewController: UIViewController {
override var tabBarItem: UITabBarItem!{
get {
return UITabBarItem.init(title: "setting", image: nil, tag: 0)
}
set{
super.tabBarItem = newValue
}
}
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.white
self.view.addSubview(backbutton)
}
}
It's clear there is only one line in the SettingVC. But if you need to use constraints, what should we do? Everything else is fine, but the position of UILabel constraints depends on the superView of UILabel. So an extension can be used here to make things easier.
let specialLabelTag = 1001
let backbutton: UILabel! = {
let button = UILabel()
button.tag = specialLabelTag
button.text = "test" // for test purpose
button.translatesAutoresizingMaskIntoConstraints = false
button.widthAnchor.constraint(equalToConstant: 50).isActive = true
button.heightAnchor.constraint(equalToConstant: 50).isActive = true
return button
}()
extension UILabel{
override open func didMoveToSuperview() {
superview?.didMoveToSuperview()
if(tag == specialLabelTag){
leftAnchor.constraint(equalTo: superview!.leftAnchor, constant: 20).isActive = true
topAnchor.constraint(equalTo: superview!.topAnchor, constant: 125).isActive = true
}
}
The tag used in extension is to identify the global UILabel in order not to affect other UILabels. Only position constraints are needed in the extension. SettingUP vc is as same as before.
Now you can build a label without any view class. But you have to add them somewhere and modify the text as you like. Hope this is the answer to the question.
BTW, you can subclass the UILabel to MyUILabel with above code and then make it global (just put outside any class). It would be much easier because you don't need to use specialLabelTag.
let backbutton = MyUILabel()

Swift iOS -ScrollView w/ StackView PageController Crash

I followed this programmatic scrollview-pageview tutorial I keep getting a crash inside the Scrollview Delegate's ScrollViewDidScroll(...)
Thread 1: EXC_BREAKPOINT (code=1, subcode=0x1030b0d98)
The crash appears on this line: pageControl.currentPage = Int(round(pageFraction))
Why does this crash keeps happening
ViewController Class:
class ViewController: UIViewController, UIScrollViewDelegate {
let scrollView: UIScrollView = {
let scrollView = UIScrollView()
scrollView.translatesAutoresizingMaskIntoConstraints = false
scrollView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
scrollView.isPagingEnabled = true
scrollView.showsHorizontalScrollIndicator = false
scrollView.backgroundColor = UIColor.white
return scrollView
}()
let stackView: UIStackView = {
let stackView = UIStackView()
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.distribution = .equalSpacing
return stackView
}()
let pageControl: UIPageControl = {
let pageControl = UIPageControl()
pageControl.translatesAutoresizingMaskIntoConstraints = false
pageControl.currentPage = 0
pageControl.tintColor = UIColor.white
pageControl.pageIndicatorTintColor = UIColor.gray
pageControl.currentPageIndicatorTintColor = UIColor.red
pageControl.addTarget(self, action: #selector(pageControlTapped(sender:)), for: .valueChanged)
return pageControl
}()
var views = [UIView]()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.white
scrollView.delegate = self
configureViewsForViewArray()
setScrollViewAnchors()
setStackViewAnchors()
setPageViewsInsideStackView()
setPageControlAnchors()
}
func configureViewsForViewArray(){
let pageView1 = PageView(headerText: "Header 1", paragraphText: "Bla bla bla", backgroundColor: .red)
views.append(pageView1)
let pageView2 = PageView(headerText: "Header 2", paragraphText: "Bla bla bla", backgroundColor: .orange)
views.append(pageView2)
let pageView3 = PageView(headerText: "Header 3", paragraphText: "Bla bla bla", backgroundColor: .blue)
views.append(pageView3)
let pageView4 = PageView(headerText: "Header 4", paragraphText: "Bla bla bla", backgroundColor: .green)
views.append(pageView4)
}
func setScrollViewAnchors(){
view.addSubview(scrollView)
if #available(iOS 11.0, *) {
scrollView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 0).isActive = true
} else {
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0).isActive = true
}
if #available(iOS 11.0, *) {
scrollView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: 0).isActive = true
} else {
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0).isActive = true
}
if #available(iOS 11.0, *) {
scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 0).isActive = true
} else {
scrollView.topAnchor.constraint(equalTo: view.topAnchor, constant: 0).isActive = true
}
if #available(iOS 11.0, *) {
scrollView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: 0).isActive = true
} else {
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0).isActive = true
}
}
func setStackViewAnchors(){
scrollView.addSubview(stackView)
stackView.leftAnchor.constraint(equalTo: scrollView.leftAnchor).isActive = true
stackView.rightAnchor.constraint(equalTo: scrollView.rightAnchor).isActive = true
stackView.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
stackView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
}
func setPageViewsInsideStackView(){
for eachView in self.views{
eachView.translatesAutoresizingMaskIntoConstraints = false
stackView.addArrangedSubview(eachView)
eachView.heightAnchor.constraint(equalTo: view.heightAnchor).isActive = true
eachView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
}
}
func setPageControlAnchors(){
view.addSubview(pageControl)
pageControl.numberOfPages = views.count
if #available(iOS 11.0, *) {
pageControl.centerXAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerXAnchor).isActive = true
} else {
// Fallback on earlier versions
pageControl.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
}
if #available(iOS 11.0, *) {
pageControl.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -20).isActive = true
} else {
// Fallback on earlier versions
pageControl.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -20).isActive = true
}
}
#objc func pageControlTapped(sender: UIPageControl) {
let pageWidth = scrollView.bounds.width
let offset = sender.currentPage * Int(pageWidth)
UIView.animate(withDuration: 0.33, animations: { [weak self] in
self?.scrollView.contentOffset.x = CGFloat(offset)
})
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let pageWidth = scrollView.bounds.width
let pageFraction = scrollView.contentOffset.x/pageWidth
//I get a crash here?
pageControl.currentPage = Int(round(pageFraction))
}
}
// this is the model for the views that get added inside the [UIView] array above
PageView:
import Foundation
import UIKit
class PageView: UIView {
// Private so that it can only be modified from within the class
private var headerTextField = UITextField()
// When this property is set it will update the headerTextField text
var headerText: String = "" {
didSet {
headerTextField.text = headerText
}
}
// Private so that you can only change from within the class
private var paragraphTextView = UITextView()
// When this property is set it will update the paragraphTextView text
var paragraphText: String = "" {
didSet {
paragraphTextView.text = paragraphText
}
}
// Designated Init method
init(headerText: String, paragraphText: String, backgroundColor: UIColor) {
super.init(frame: .zero)
setup()
self.headerTextField.text = headerText
self.paragraphTextView.text = paragraphText
self.backgroundColor = backgroundColor
}
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setup() {
// Basic text and view setup
headerTextField.isUserInteractionEnabled = false
headerTextField.textColor = .black
headerTextField.textAlignment = .center
headerTextField.sizeToFit()
paragraphTextView.isUserInteractionEnabled = false
paragraphTextView.textColor = .black
paragraphTextView.textAlignment = .center
paragraphTextView.sizeToFit()
paragraphTextView.isScrollEnabled = false
paragraphTextView.backgroundColor = .clear
// Configuring the textfield/view for autoLayout
headerTextField.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(headerTextField)
paragraphTextView.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(paragraphTextView)
// Creating and activating the constraints
NSLayoutConstraint.activate([
headerTextField.centerXAnchor.constraint(equalTo: self.centerXAnchor),
headerTextField.centerYAnchor.constraint(equalTo: self.centerYAnchor),
paragraphTextView.centerXAnchor.constraint(equalTo: self.centerXAnchor),
paragraphTextView.topAnchor.constraint(equalTo: headerTextField.bottomAnchor, constant: 20),
paragraphTextView.widthAnchor.constraint(equalTo: self.widthAnchor, multiplier: (2/3))
])
}
}

Resources