1

As a simple starting point. I'm trying to create a custom button that has an activity indicator in the middle. After a tap - it will indicate that it's thinking

I'm pretty much stuck at step 1 - adding the indicator to be centred in the surrounding view. No matter want constraints I try it always appears in the top left corner.

What am I missing?

Button Fail

Here's my playground code.

//: Playground - noun: a place where people can play

import PlaygroundSupport
import UIKit

class ConnectButton : UIView {

    fileprivate let activityIndicator: UIActivityIndicatorView = {
        let activityIndicator = UIActivityIndicatorView()

        activityIndicator.color = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1)
        activityIndicator.hidesWhenStopped = true;
        activityIndicator.stopAnimating();
        activityIndicator.activityIndicatorViewStyle = .white;
        return activityIndicator;
    }()


    private func initView() {
        translatesAutoresizingMaskIntoConstraints = true;
        addSubview(activityIndicator);

        NSLayoutConstraint.activate([
            activityIndicator.centerYAnchor.constraint(equalTo: self.centerYAnchor),
            activityIndicator.leftAnchor.constraint(equalTo: self.centerXAnchor)
        ])
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder);
        self.initView();
    }

    override init(frame: CGRect) {
        super.init(frame: frame);
        self.initView();
    }

    public func startAnimating() {
        activityIndicator.startAnimating();
    }

    public func stopAnimating() {
        activityIndicator.stopAnimating();
    }
}

let dimensions = (width:200, height: 50);
let connectButton = ConnectButton(
    frame: CGRect(
        x: dimensions.width / 2,
        y: dimensions.height / 2,
        width: dimensions.width,
        height: dimensions.height
    )
)

connectButton.startAnimating();
connectButton.backgroundColor = #colorLiteral(red: 0.1764705926, green: 0.4980392158, blue: 0.7568627596, alpha: 1);

PlaygroundPage.current.liveView = connectButton
MickCrozier
  • 312
  • 1
  • 8
  • add self.view.layoutIfNeeded() or activityIndicator.layoutIfNeeded() to update the constraint changes. However since I don't code in Swift I really don't understand what self.centerYAnchor is, I don't see it anywhere in your code, neither self.centerXAnchor. If you change these constraints in your viewDidLoad methods, remember that your superView constraints have not been calculated when you are in viewDidLoad yet, unless you have specific height/width set for it already. –  Feb 28 '17 at 01:35
  • I added self.layoutIfNeeded() and activityIndicator.layoutIfNeeded() to the initView, and then last in the playground code - No change – MickCrozier Feb 28 '17 at 01:36
  • Add it to your superView when you have initialized the subView. –  Feb 28 '17 at 01:36
  • I'm creating this directly inside a view as reusable custom control. There is no UIViewController (I'm initialising in a playground for prototyping - I assume last in the playground code would be similar effect). I'm using translatesAutoresizingMaskIntoConstraints = true. I can remove this and place actual height and width constants in - but has same 'not centering' effect. – MickCrozier Feb 28 '17 at 01:41
  • Hmm, did you read tomahhs answer below? I will add another solution you can try to simplify things –  Feb 28 '17 at 01:43

2 Answers2

3

You want to set translatesAutoresizingMaskIntoConstraints to false onto UIActivityIndicatorView. Otherwise, UIKit will create constraints based on the resizing masks set on the, which will conflict with the ones you already added.

fileprivate let activityIndicator: UIActivityIndicatorView = {
    let activityIndicator = UIActivityIndicatorView()

    activityIndicator.color = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1)
    activityIndicator.hidesWhenStopped = true
    activityIndicator.stopAnimating()
    activityIndicator.activityIndicatorViewStyle = .white
    activityIndicator. translatesAutoresizingMaskIntoConstraints = false
    return activityIndicator
}()
tomahh
  • 13,441
  • 3
  • 49
  • 70
  • Upvoted, but You should clarify in your answer what you mean with as-well btw, since it wont be obvious for people reading it. –  Feb 28 '17 at 01:48
  • 1
    Good call. I misread the code in the question and though op was setting them to `false` in `initView`. Answer updated :) – tomahh Feb 28 '17 at 01:50
  • Ahh - bang. That's it. Thanks @tomahh – MickCrozier Feb 28 '17 at 02:20
0

Another solution: Create your UIActivityIndicator with a given frame from your superView as you already do, and simply call:

activityIndicator.center = self.center

And avoid the NSLayoutConstraints alltogether, unless you really need them for something.

However, as I mentioned in the commentsection, make sure you have your UIView laid out to the correct size before calling above line. Otherwise your result will be exactly the same.

  • Had a play with this earlier. Problem is for later code things might not always be centered and need to be flexible to deal with screen sizes and rotation – MickCrozier Feb 28 '17 at 02:22
  • @MickCrozier I see, If you really want to avoid all the mess, I suggest you use the storyboard, create a XIB and set it to your UIView subclass. Add the views and constraints from the storyboard you will have total visual control. When you want to add it to your superView you can simply just load it with one row code like this: http://stackoverflow.com/questions/35659714/loading-a-xib-file-to-a-uiview-swift . Also, put all your code inside the https://developer.apple.com/reference/objectivec/nsobject/1402907-awakefromnib instead of initFrame. –  Feb 28 '17 at 02:29