changing the multiplier value of a constraint - swift

I have a menu bar which sits at the top of a CollectionViewController with several different titles about 30 pixels high
. Each title has a small bar underneath, indicating which page/item the user is on, similar to the Youtube app.
class MenuBar : UIView {
var menuSectionTitles = ["Title1","Title2", "Title3","Title4"]
var numberOfSectionsInHorizontalBar : CGFloat?
var horizontalBarLeftAnchorConstraint: NSLayoutConstraint?
var horizontalBarWidthAnchorConstraint: NSLayoutConstraint?
override init(frame: CGRect) {
super.init(frame: frame)
setupHorizontalBar()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupHorizontalBar() {
let horizontalBarView = UIView()
horizontalBarView.backgroundColor = UIColor.rgb(10, green: 150, blue: 255)
horizontalBarView.translatesAutoresizingMaskIntoConstraints = false
addSubview(horizontalBarView)
horizontalBarLeftAnchorConstraint = horizontalBarView.leftAnchor.constraint(equalTo: self.leftAnchor)
horizontalBarLeftAnchorConstraint?.isActive = true
horizontalBarView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
// Set the multiplier/width of the horizontal bar to indicate which page the user is on
horizontalBarWidthAnchorConstraint = horizontalBarView.widthAnchor.constraint(equalTo: widthAnchor, multiplier: 1 / 4)
horizontalBarWidthAnchorConstraint?.isActive = true
horizontalBarView.heightAnchor.constraint(equalToConstant: 3).isActive = true
}
}
The multiplier value for horizontalBarWidthAnchorConstraint is hard coded in at a value of 1/4
I want to reuse the view multiple times across different view controllers, therefore have attempted to change the multiplier of the horizontalBarWidthAnchorConstraint like so:
class ViewController: UICollectionViewController {
lazy var menuBar : MenuBar = {
let mb = OptionsBar()
mb.menuOptions = ["Title1", "Title2", "Title3"]
mb.translatesAutoresizingMaskIntoConstraints = false
mb.horizontalBarWidthAnchorConstraint?.constant = 1/3
return mb
}()
}
But this method does not work. I can not find a way to change the multiplier value from inside the ViewController.

In the code here you are changing the constant of the constraint, not the multiplier. Is that correct?
Also note, the multiplier of a constraint is read only. You cannot change it. If you want to change the multiplier of a constraint applying to a view then the only to do it is to add a new constraint with the new multiplier and remove the old constraint.
The only part of a constraint that is read/write is the constant property.

Related

How to change the width of an NSView in a transparent window

(Swift, macOS, storyboard)
I have an NSView in a transparent window
I have this in the viewDidLoad. To make the window transparent and the NSView blue:
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2){
self.view.window?.isOpaque = false
self.view.window?.backgroundColor = NSColor.clear
}
view1.wantsLayer = true
view1.layer?.backgroundColor = NSColor.green.cgColor
I want to change the width with code when I click a button.
If it has constraints:
#IBAction func button1(_ sender: NSButton) {
view1Width.constant = 74
}
I tried without constraints and different ways to change the width. They all give the same results:
view1.frame = NSRect(x:50, y:120, width:74, height:100)
But there is still a border and a shadow where the old shape was. Why does it happen and how to solve it?
It only happens in specific circumstances:
If the window is transparent (and macOS)
I change the width and do not change the position y
The window must be active. If it is not (If I click to anywhere else) it looks as it should: the shadow around the changed NSView green.
(I have simplified the case to try to find a solution. I have created a new document and there is only this code and I am sure there is no other element)
Since the window is transparent you need to invalidate the shadows.
Apple states about invalidateShadow()
Invalidates the window shadow so that it is recomputed based on the current window shape.
Complete Self-Contained Test Program
It sets up the UI pogrammatically instead of using a storyboard. Other than that, the code is very close to your example.
Note the line:
view.window?.invalidateShadow()
in the onChange method.
import Cocoa
class ViewController: NSViewController {
private let view1 = NSView()
private let changeButton = NSButton()
private var view1Width: NSLayoutConstraint?
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2){
self.view.window?.isOpaque = false
self.view.window?.backgroundColor = NSColor.clear
}
view1.wantsLayer = true
view1.layer?.backgroundColor = NSColor.green.cgColor
}
#objc private func onChange() {
view1Width?.constant += 32
view.window?.invalidateShadow()
}
private func setupUI() {
changeButton.title = "change"
changeButton.bezelStyle = .rounded
changeButton.setButtonType(.momentaryPushIn)
changeButton.target = self
changeButton.action = #selector(onChange)
self.view.addSubview(view1)
self.view.addSubview(changeButton)
self.view1.translatesAutoresizingMaskIntoConstraints = false
self.changeButton.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
view1.centerXAnchor.constraint(equalTo: view.centerXAnchor),
view1.centerYAnchor.constraint(equalTo: view.centerYAnchor),
view1.heightAnchor.constraint(equalToConstant: 128),
changeButton.topAnchor.constraint(equalTo: view1.bottomAnchor, constant:16),
changeButton.centerXAnchor.constraint(equalTo: view1.centerXAnchor)
])
view1Width = view1.widthAnchor.constraint(equalToConstant: 128)
view1Width?.isActive = true
}
}
Result
The desired result with an update of the shadows is accomplished:

How to create block screen with circle loader

I am doing an app that does background job that can take some time
I want to show a loader in that time
I want a black screen with a simple loader in the front of it
and show it \ hide it,
when I do actions in the background
I want to do a simple half black square with loader circle
that also blocks presses to the screen
Like in this picture:
How can I achieve that and that ?
First create one UIView which you will put in front of your LogIn view. Then add UIActivityIndicatorView to the created UIView.
let loadingIndicatorView = UIView()
let activityIndicatorView = UIActivityIndicatorView(activityIndicatorStyle: .gray)
Now the loadingIndicatorView should have same frame size as your LogIN view. For color you can set your own color with alpha as you want to show LogIn content too. Initially keep it hidden and whenever you want to show it unhide it.
loadingIndicatorView.frame = view.frame
loadingIndicatorView.backgroundColor = .gray
loadingIndicatorView.isHidden = true
Now setup activityIndicatorView, it should be shown at centre,
activityIndicatorView.center = CGPoint(
x: UIScreen.main.bounds.size.width / 2,
y: UIScreen.main.bounds.size.height / 2
)
You can set some color to the indicator,
activityIndicatorView.color = .white
activityIndicatorView.hidesWhenStopped = true
Now add this activityIndicatorView to loadingIndicatorView and loadingIndicatorView to LogIn View.
loadingIndicatorView.addSubview(activityIndicatorView)
view.addSubview(loadingIndicatorView)
Lastly for showing do,
loadingIndicator.startAnimating()
loadingIndicatorView.isHidden = false
And for hiding,
loadingIndicator.stopAnimating()
loadingIndicatorView.isHidden = true
Updated Answer
Since the OP wanted an example code. Hence the updated answer. Hope everyone gets to learn something or the other out of it.
To start with, I created a subclass of UIView and named it PSOverlaySpinner and it looks something like below:
import UIKit
class PSOverlaySpinner: UIView {
//MARK: - Variables
private var isSpinning: Bool = false
private lazy var spinner : UIActivityIndicatorView = {
var spinner = UIActivityIndicatorView(style: UIActivityIndicatorView.Style.white)
spinner.translatesAutoresizingMaskIntoConstraints = false
spinner.hidesWhenStopped = true
return spinner
}()
// MARK: - View Lifecycle Functions
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
init() {
super.init(frame: CGRect.zero)
self.translatesAutoresizingMaskIntoConstraints = false
self.backgroundColor = UIColor.init(white: 0.0, alpha: 0.8)
self.isSpinning = false
self.isHidden = true
createSubviews()
}
deinit {
self.removeFromSuperview()
}
func createSubviews() -> Void {
self.addSubview(spinner)
setupAutoLayout()
}
// MARK: - Private Methods
private func setupAutoLayout() {
if #available(iOS 11.0, *) {
spinner.safeAreaLayoutGuide.centerXAnchor.constraint(equalTo: safeAreaLayoutGuide.centerXAnchor).isActive = true
spinner.safeAreaLayoutGuide.centerYAnchor.constraint(equalTo: safeAreaLayoutGuide.centerYAnchor).isActive = true
} else {
// Fallback on earlier versions
spinner.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
spinner.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
}
}
// MARK: - Public Methods
public func show() -> Void {
DispatchQueue.main.async {
if !self.spinner.isAnimating {
self.spinner.startAnimating()
}
self.isHidden = false
}
isSpinning = true
}
public func hide() -> Void {
DispatchQueue.main.async {
if self.spinner.isAnimating {
self.spinner.stopAnimating()
}
self.isHidden = true
}
isSpinning = false
}
}
Now move onto the ViewController that you want to add this overlay view to. Since I create my views programmatically, I will show how to do it the same way, but you can easily do it via storyboard or xibs.
Step 1 : Initialize
public lazy var spinnerView : PSOverlaySpinner = {
let loadingView : PSOverlaySpinner = PSOverlaySpinner()
return loadingView
}()
Step 2 : Add as a subview
self.view.addSubview(spinnerView)
Step 3 : Set constraints
spinnerView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true
spinnerView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true
spinnerView.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
spinnerView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
Step 4 : To show PSOverlaySpinner
spinnerView.show()
Step 5 : To hide PSOverlaySpinner
spinnerView.hide()
That is it!!
If you want you can go ahead and modify the PSOverlaySpinner as per your needs. For example, you might want to add a UILabel below the spinner indicating him of the type of action taking place and so on.
Before
After
Old Answer
If you wish to do it manually then create a UIView with the its frame matching self.view.bounds, with 0.5-0.7 alpha and black background color. Add UIActivityIndicator as its subview constrained to its center. For a spinner specific to the image you will have to use the open sourced spinners made available. A couple of them can be found here. Once done add this view as the topmost subview in self.view.
You need to import this library SVProgressHUD and then set few properties like as follows:
SVProgressHUD.setDefaultStyle(SVProgressHUDStyle.dark)
SVProgressHUD.setBackgroundColor(.clear)
SVProgressHUD.setForegroundColor(.white)
SVProgressHUD.setDefaultMaskType(.black)
SVProgressHUD.show()
//SVProgressHUD.show(withStatus: "Loading something, Loading something,Loading something ...")
This will produce same UI output as needed by you in OP. You can find a running sample at my repository (TestPreLoader)

Label Colours on an #IBDesignable View appearing different of different devices

I have an #IBDesignable custom view. It contain an UIView with two subclassed UILabels. The custom subclass of UILabel is setting it's font.
What I'm trying to achieve is by changing by making the background colour of the view an inspectable property that the text colour change appropriately to be legible.
My code is below. Custom.Colour.<name> is just an enum of defined colours.
#IBDesignable
class CustomMiniView: UIView, NibLoadable {
public var view:UIView!
#IBOutlet weak var colourView: UIView!
#IBOutlet weak var headingLabel: CustomUILabel!
#IBOutlet weak var amountLabel: CustomUILabel!
#IBInspectable var blockColor:UIColor! {
didSet {
self.colourView.backgroundColor = blockColor
switch blockColor {
case Custom.Colour.darkBlue:
headingLabel.textColor = .white
amountLabel.textColor = .white
case Custom.Colour.blue:
headingLabel.textColor = .white
amountLabel.textColor = .white
case Custom.Colour.lightBlue:
headingLabel.textColor = Custom.Colour.grey
amountLabel.textColor = Custom.Colour.blue
case Custom.Colour.green:
headingLabel.textColor = .white
amountLabel.textColor = .white
case Custom.Colour.lightGreen:
headingLabel.textColor = Custom.Colour.grey
amountLabel.textColor = Custom.Colour.blue
case Custom.Colour.yellow:
headingLabel.textColor = Custom.Colour.grey
amountLabel.textColor = Custom.Colour.grey
default : printError("We have not handled text colours for a background of this colour. = \(blockColor.hexString)")
}
}
}
public override init(frame: CGRect) {
super.init(frame: frame)
self.setupFromNib()
self.commonInit()
}
public required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.setupFromNib()
self.commonInit()
}
func commonInit() {
self.backgroundColor = .clear
}
}
This was working for me fine, however I was then getting report that the text was showing up as white everywhere and got sent a screen shot which confused me. When I ran this in a simulator and a different device I was able to see this not working. Here are two screen shots of what is happening on my iPad and what I expect to happen and a screen shot of what is happening on some other devices and the simulator.
This is what is happening on my device and the expected result.
This is whats happening on other devices and the incorrect result.
Is there a reason this would appeared different of different devices? I'm at a loss to the cause or how to fix this.
Thanks to comments above I've found a fix.
Rather than doing a switch on the colour I've done a switch on the .hexString of the colour which has worked.
switch blockColor.hexString {
case Custom.Colour.darkBlue.hexString :
for the record hexString is an extension to UIColor
var hexString: String {
let colorRef = cgColor.components
let r = colorRef?[0] ?? 0
let g = colorRef?[1] ?? 0
let b = ((colorRef?.count ?? 0) > 2 ? colorRef?[2] : g) ?? 0
let a = cgColor.alpha
var color = String(
format: "#%02lX%02lX%02lX",
lroundf(Float(r * 255)),
lroundf(Float(g * 255)),
lroundf(Float(b * 255))
)
if a < 1 {
color += String(format: "%02lX", lroundf(Float(a)))
}
return color
}
I'm still not sure why switching on hexString worked and the UIColor in the enum did not work consistently but thanks to comments about that helped me find a solution to fix it myself. Question for another time is why switching on UIColor would be unreliable but now back to work.

Nested UITableView dynamic height

I need to have a nested UITableView in a tableView cell (actually, two tables in one cell) to show different lists with dynamic content (so, I need dynamic heights). My nested tables will not have scrolling—I need them just to order elements of different kinds, like texts, pictures, fields etc. To be more clear—the first level is the level of operations and every operation can have a variable amount of instructions and actions. Instructions and actions should be placed side by side and the operation cell should have the size of the tallest table.
There are no problems in nesting tables, but I faced a problem with the auto layout. I’ve tried everything that I could find, but with no success.
I tried height constraints for nested table views, which I update on the operation cell creation from tableview.contentsize.hight, but it seems that contentsize returns height based on the estimated size of every row, but not the actual size.
I tried to rewrite intrinsic content size of nested tables:
UITableView {
override var contentSize:CGSize {
didSet {
self.invalidateIntrinsicContentSize()
}
}
override var intrinsicContentSize: CGSize {
self.layoutIfNeeded()
return CGSize(width: UIViewNoIntrinsicMetric, height: contentSize.height)
}
}
Nothing works properly. Any ideas how it could be solved?
Thank you in advance.
Set Inner tableview my custom class AGTableView and height Constraint both are required,
this class set contantSize same table view height Constraint.
Check out Github AutoHeightIncrementTableViewDemo
class AGTableView: UITableView {
fileprivate var heightConstraint: NSLayoutConstraint!
override init(frame: CGRect, style: UITableViewStyle) {
super.init(frame: frame, style: style)
self.associateConstraints()
defaultInit()
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.associateConstraints()
defaultInit()
}
func defaultInit(){
self.keyboardDismissMode = .onDrag
self.showsVerticalScrollIndicator = false
self.showsHorizontalScrollIndicator = false
self.tableFooterView = UIView(frame: .zero)
self.tableHeaderView = UIView(frame: .zero)
self.sectionFooterHeight = 0
self.sectionHeaderHeight = 0
}
override open func layoutSubviews() {
super.layoutSubviews()
if self.heightConstraint != nil {
self.heightConstraint.constant = self.contentSize.height
}
else{
print("Set a heightConstraint to set cocontentSize with same")
}
}
func associateConstraints() {
// iterate through all text view's constraints and identify
// height
for constraint: NSLayoutConstraint in constraints {
if constraint.firstAttribute == .height {
if constraint.relation == .equal {
heightConstraint = constraint
}
}
}
}
}
NOTE: Also set a estimatedRowHeight
self.rowHeight = UITableViewAutomaticDimension
self.estimatedRowHeight = height

Stackview label not adding programmatically

I'm trying to do everything without a story board but keep getting nil when adding my label but I can't see why. Im sure its dead simple and I'm being blind but I just can't see it after a lot of googling.
View Controller:
class SquaresViewController: UIViewController {
let stackView = OAStackView()
let titleView = TitleView(frame: CGRect.zero)
#IBOutlet var squaresCollectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
stackView.axis = .vertical
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.addArrangedSubview(titleView)
titleView.snp.makeConstraints { make in
make.height.equalTo(50)
make.top.equalTo(view.snp.top)
make.left.equalTo(view.snp.left)
make.right.equalTo(view.snp.right)
}
}
}
titleView:
import UIKit
class TitleView: UIView {
#IBOutlet weak var titleText: UILabel!
override init(frame: CGRect) {
super.init(frame: frame)
self.translatesAutoresizingMaskIntoConstraints = false
createView()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func createView() {
titleText = UILabel(frame: CGRect.zero)
titleText.translatesAutoresizingMaskIntoConstraints = false
titleText.text = "Tappy tap tap a square!"
titleText.textAlignment = .center
titleText.numberOfLines = 0
self.addSubview(titleText)
titleText.snp.makeConstraints { make in
make.edges.equalTo( self.snp.edges )
}
}
}
When building it fails at:
titleText.translatesAutoresizingMaskIntoConstraints = false
with:
fatal error: unexpectedly found nil while unwrapping an Optional value
First of all, I don't understand why you created an IBOutlet for titleText if you don't want to use storyboards. If you don't want to use storyboards, change the implementation to:
var titleText : UILabel!
Secondly, the application is crashing because you are asking it to perform an operation on a view that is not yet added to the hierarchy. For ease of reading I have commented out all lines except those that are important. See below:
func createView() {
//titleText = UILabel(frame: CGRect.zero)
titleText.translatesAutoresizingMaskIntoConstraints = false // <-- You are trying to perform an operation on titleText here
//titleText.text = "Tappy tap tap a square!"
//titleText.textAlignment = .center
//titleText.numberOfLines = 0
self.addSubview(titleText) // <-- but you are adding it to the view hierarchy here
//titleText.snp.makeConstraints { make in
//make.edges.equalTo( self.snp.edges )
//}
}
titleText has to be added to the view hierarchy before you can call translatesAutoresizingMaskIntoConstraints on it, like so:
self.view.addSubview(titleText)
titleText.translatesAutoresizingMaskIntoConstraints = false // <-- Here we are waiting until the view has been added to the hierarchy to perform operations on it
So try changing implementation to look like so:
import UIKit
class TitleView: UIView {
var titleText: UILabel!
override init(frame: CGRect) {
// leave as is, removed for space
}
required init?(coder aDecoder: NSCoder) {
// leave as is, removed for space
}
func createView() {
titleText = UILabel(frame: CGRect.zero)
titleText.text = "Tappy tap tap a square!"
titleText.textAlignment = .center
titleText.numberOfLines = 0
self.addSubview(titleText)
titleText.translatesAutoresizingMaskIntoConstraints = false
titleText.snp.makeConstraints { make in
make.edges.equalTo( self.snp.edges )
}
}
}