0

I'm currently running into problems with custom UIViews nested in other custom UIViews, with the variables of the nested custom UIView not being able to be changed and hence reflected in the view itself.

Currently, I have 3 custom UIViews:

TokenView, which only has drawRect overridden to make a view with an image in a circle.

@IBDesignable class TokenView: UIView {

@IBInspectable var tokenOutlineColor: UIColor = UIColor.blackColor()
@IBInspectable var tokenBackgroundColor: UIColor = UIColor.lightGrayColor()
@IBInspectable var tokenImage: UIImage!

override init(frame: CGRect){
    super.init(frame:frame)
}

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

override func drawRect(rect: CGRect) {

    var context = UIGraphicsGetCurrentContext()

    var thickness: CGFloat = 3
    var path = UIBezierPath(ovalInRect: CGRect(origin: CGPointMake(thickness/2, thickness/2), size: CGSize(width: rect.width - thickness, height: rect.height - thickness)))

    CGContextSaveGState(context)
    path.addClip()

    tokenOutlineColor.setStroke()
    tokenBackgroundColor.setFill()
    path.fill()
    if let image = tokenImage {
        tokenImage.drawInRect(rect)
    }
    path.stroke()

    }
}

PersonView, which has a TokenView() as a subview, and works as intended as well, when I change my variables of said TokenView.

class PersonView: UIView {

var name: String?
var group: PersonGroup?

required init?(coder aDecoder: NSCoder){
    super.init(coder: aDecoder)
    makePersonView()
}

override init(frame: CGRect) {
    super.init(frame: frame)
    makePersonView()
}

func makePersonView(){

    if name == nil {
        self.name = "Bob"
    }
    if group == nil {
        self.group = PersonGroup.Good
    }

    let token = TokenView()
    token.translatesAutoresizingMaskIntoConstraints = false

    switch group! {
    case .Good:
        token.tokenOutlineColor = UIColor.greenColor()
    case .Bad:
        token.tokenOutlineColor = UIColor.redColor()
    default:
        token.tokenOutlineColor = UIColor.blueColor()
    }
    token.tokenImage = UIImage(named: name!)
    token.tokenBackgroundColor = UIColor.clearColor()
    token.backgroundColor = UIColor.clearColor()        
    addSubview(token)

    let label = UILabel()
    label.text = name
    label.translatesAutoresizingMaskIntoConstraints = false
    label.textAlignment = NSTextAlignment.Center
    addSubview(label)

    NSLayoutConstraint.activateConstraints([
        token.topAnchor.constraintEqualToAnchor(topAnchor),
        token.leadingAnchor.constraintEqualToAnchor(leadingAnchor),
        token.trailingAnchor.constraintEqualToAnchor(trailingAnchor),
        token.heightAnchor.constraintEqualToConstant(frame.size.height*0.70),
        token.bottomAnchor.constraintEqualToAnchor(label.topAnchor),
        label.leadingAnchor.constraintEqualToAnchor(leadingAnchor),
        label.trailingAnchor.constraintEqualToAnchor(trailingAnchor),
        label.bottomAnchor.constraintEqualToAnchor(bottomAnchor)
        ])
}

and DetailedPersonView class, whereby a PersonView instance is created and its variables being modified from there, and that is where the problem comes about. The other added "details" work fine, just not the ones in the nested PersonView when modified in a function in the class.

class DetailedPersonView: UIView {

var name: String = "Marley"
var group: PersonGroup = PersonGroup.Bad

var quantity: Float = 0.00
var stat: String? = "Strength"

required init?(coder aDecoder: NSCoder){
    super.init(coder: aDecoder)
    makeDetailedPersonView()

}

override init(frame: CGRect) {
    super.init(frame: frame)
    makeDetailedPersonView()
}

func makeDetailedPersonView(){
    let personView = PersonView()
    personView.translatesAutoresizingMaskIntoConstraints = false
    personView.name = name
    personView.group = group
    personView.type = type
    addSubview(personView)

    let label = UILabel()
    let quantityString = String(format: quantity == floor(quantity) ? "%.0f":"%.1f", quantity)
    if unit != nil {
        label.text = quantityString + " " + stat!
    } else {
        label.text = quantityString
    }
    label.translatesAutoresizingMaskIntoConstraints = false
    label.textAlignment = NSTextAlignment.Center

    addSubview(label)

    NSLayoutConstraint.activateConstraints([
        personView.topAnchor.constraintEqualToAnchor(topAnchor),
        personView.leadingAnchor.constraintEqualToAnchor(leadingAnchor),
        personView.trailingAnchor.constraintEqualToAnchor(trailingAnchor),
        personView.bottomAnchor.constraintEqualToAnchor(label.topAnchor),
        label.leadingAnchor.constraintEqualToAnchor(leadingAnchor),
        label.trailingAnchor.constraintEqualToAnchor(trailingAnchor),
        label.heightAnchor.constraintEqualToConstant(frame.size.height*0.25),
        label.bottomAnchor.constraintEqualToAnchor(bottomAnchor)
        ])

}


}

So currently my DetailedPersonView has its TokenView (nested within PersonView) showing PersonView's default tokenName and tokenImage, being "Bob", same goes for the circle's outline reflecting PersonGroup.Bad .

I have tried using custom init methods for PersonView, to try to initialize a PersonView in a DetailedPersonView with the variables of DetailedPersonView, but to limited success.

Ideally, this custom subview should work similar to how UILabel does, where UILabel().text changes its text in the view as its variables changes, if that makes sense.

So far haven't found a clear solution to this problem yet :\

Thank you for reading this question! :D

EDIT: One workaround that I thought might work is to implement a class method to the custom UIView to reset each subview's contents and call it every time a variable has been changed. Or is there already a function in a delegate that can achieve the same results, and is called whenever the view is changed?

1 Answer 1

2

You are changing properties on the subview but not propagating those changes to the subviews that depend on those properties. In swift this is typically done with property observing. See the property observers section here for more info. But you can use the didSetobserver to propagate your changes. For example, in your PersonView class you should first keep a reference to it's TokenView subview var tokenView:TokenView? and it's UILabel subviewvar nameLabel:UILabel?and set them equal to theTokenViewandUILabelyou create inmakePersonView`. Then add property observing on name:

    var name: String?{
        didSet{
            if let image = UIImage(named: name!)
            {
                self.tokenView?.tokenImage = image
                self.nameLabel?.text = name
                self.tokenView?.setNeedsDisplay()
            }
        }
    }

This way any changes to name by DetailPersonView will automatically change TokenView's image.

Similarly with tokenImage and tokenOutlineColor you need to redraw your view each time they are set.

   @IBInspectable var tokenOutlineColor: UIColor = UIColor.blackColor(){
        didSet{
            self.setNeedsDisplay()
        }
    }
    @IBInspectable var tokenImage: UIImage!{
        didSet{
            self.setNeedsDisplay()
        }
    }
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.