Ib_Designable, Ibinspectable -- Interface Builder Does Not Update

xcode Interface Builder not updating IBDesignable class

I solved the issue with help from Reinier's 2nd link and a course I previously took on Udemy.

I implemented my customisation in a setupView() function. I called this in prepareForInterfaceBuilder() and also awakeFromNib()

import UIKit

@IBDesignable class RoundedTextField: UITextField {

override func awakeFromNib() {
super.awakeFromNib()
setupView()
}

override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
self.setupView()
}

// Placeholder text indent
override func textRect(forBounds bounds: CGRect) -> CGRect {
return bounds.insetBy(dx: 20, dy: 5)
}

// Editing text indent
override func editingRect(forBounds bounds: CGRect) -> CGRect {
return bounds.insetBy(dx: 20, dy: 5)
}

func setupView() {
layer.cornerRadius = 25
layer.borderWidth = 2
layer.borderColor = #colorLiteral(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0)
clipsToBounds = true
textColor = #colorLiteral(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0)

// Placeholder colour
attributedPlaceholder = NSAttributedString(string: placeholder!, attributes: [NSAttributedStringKey.foregroundColor: #colorLiteral(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0)])
}
}

IB_DESIGNABLE, IBInspectable -- Interface builder does not update

Based on chrisco's suggestion to debug the selected view (which I had already done, but went to try again for good measure), I noticed a couple of other options at the bottom of the Editor menu.

  • Automatically Refresh Views
  • Refresh All Views

I clicked "Refresh All Views" and after Xcode had a bit of a think, suddenly the storyboard was displaying my view as expected (properly applying my IBInspectable properties).

Sample Image

I then went through the whole process again to confirm that this is the solution.

I created a new class, ThirdView. This class is identical to the others, again. I changed my view's class to ThirdView and got something slightly different this time:

Sample Image

Clicking "Show" to me to the warnings:

Sample Image

A new one this time:

Using class UIView for object with custom class because the class ThirdView does not exist.

This isn't really any more helpful than what already existed. Plus, now the other three warnings have doubled into 6 strangely.

Anyway, if I click "Refresh All Views" from the Editor drop down menu again, all the errors go away, and once again, the view properly displays.

Still, up to this point, everything I did was stuff I never messed with at home. At home, it just worked. So I turned on "Automatically Refresh Views" and created a "FourthView" to test--once again, identical to the first three.

After changing the view's class to "FourthView" the designables label said "Updating" for a short moment then finally said "Up to date":

Sample Image

So, I checked my computer at home. "Automatically Refresh Views" is turned on at the computer that was always working. It was turned off at the computer that wasn't. I don't ever remember touching this menu option. I can't even tell you for sure whether it existed before Xcode 6. But this option is what was making the difference.


TL;DR, if you're having the same problem described in the question, make sure "Automatically Refresh Views" is turned on (or manually "Refresh All Views" when you need an update in IB):

Sample Image

IBInspectable not updating in storyboard but works on simulator

Firstly, you need @IBDesignable directive to render the updates in Storyboard whereas @IBInspectable is only to access the property directly in the Storyboard.

Secondly, you have extended the UIView class to show the inspectable properties in the Storyboard but without @IBDesignable.

Now you would think adding @IBDesignable would solve your problem but sadly you can't apply @IBDesignable on an extension like so:

@IBDesignable //won't work on an extension
extension UIView {
//...
}

You can only apply the @IBDesignable directive on a class you have access to, like so:

@IBDesignable
class MyPrettyDesignableView: UIView {
//...
}


Solution:

Bottomline is that you should subclass UIView and apply the @IBDesignable directive on this class.

Basically, your only option is:

@IBDesignable
class MyPrettyDesignableView: UIView {

@IBInspectable var cornerRadius: CGFloat = 0 {
didSet {
self.layer.cornerRadius = cornerRadius
self.layer.masksToBounds = true
}
}

@IBInspectable var borderWidth: CGFloat = 0 {
didSet {
self.layer.borderWidth = borderWidth
}
}

@IBInspectable var borderColor: UIColor = #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1) {
didSet {
self.layer.borderColor = borderColor.cgColor
}
}

}

Then in the storyboard, go to the view that you want designable in storyboard and change it's custom class to MyPrettyDesignableView.

  1. Change it's custom class to your IBDesignable subclass:
    IBDesignable

  2. Set the IBInspectable properties:
    IBInspectable

IBInspectable variable value is not changing in interface builder and simulator

A couple of thoughts:

  1. As Daniel said, you really want to call drawParallelgram in your offset observer.

  2. Also, in layoutSubviews, you’re updating the frame of your shape layer. You want to reset its path and update your mask again.

  3. You’re just adding more and more strokes to your UIBezierPath. You probably just want to make it a local variable and avoid adding more and more strokes to the existing path.

  4. The prepareForInterfaceBuilder suggests some misconception about the purpose of this method. This isn’t for doing initialization when launching from IB. This is for doing some special configuration, above and beyond what is already done by the init methods, required by IB.

    E.g. If you have a sophisticated chart view where you’ll be programmatically providing real chart data programmatically later, but you wanted to see something in IB, nonetheless, you might have prepareForInterfaceBuilder populate some dummy data, for example. But you shouldn’t repeat the configuration you already did in init methods.

  5. It’s not relevant here (because I’m going to suggest getting rid of these init methods), but for what it’s worth, if I need to do configuration during init, I generally write two init methods:

    override init(frame: CGRect = .zero) {
    super.init(frame: frame)

    <#call configure routine here#>
    }

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

    <#call configure routine here#>
    }

    Note that in init(frame:) I also supply a default value of .zero. That ensures that I’m covered in all three scenarios:

    • CustomView()
    • CustomView(frame:); or
    • if the storyboard calls CustomView(decoder:).
       

    In short, I get three init methods for the price of two. Lol.


All that being said, you can greatly simplify this:

@IBDesignable public class CustomParallelogramView: UIView {

@IBInspectable public var offset: CGFloat = 10 { didSet { updateMask() } }

override public func layoutSubviews() {
super.layoutSubviews()
updateMask()
}

private func updateMask() {
let path = UIBezierPath()
path.move(to: CGPoint(x: bounds.minX + offset, y: bounds.minY))
path.addLine(to: CGPoint(x: bounds.maxX, y: bounds.minY))
path.addLine(to: CGPoint(x: bounds.maxX - offset, y: bounds.maxY))
path.addLine(to: CGPoint(x: bounds.minX, y: bounds.maxY))
path.close()

let shapeLayer = CAShapeLayer()
shapeLayer.path = path.cgPath
layer.mask = shapeLayer
}
}

Trouble with IBInspectable and loading a view from xib for Interface Builder

Your code works fine (for the most part).

Make sure you have set the Class on the correct object:

Sample Image

Change your setupFromNib() func to:

func setupFromNib() {
guard let view = Self.nib.instantiate(withOwner: self, options: nil).first as? UIView else { fatalError("Error loading \(self) from nib") }

addSubview(view)
view.frame = self.bounds
view.autoresizingMask = [.flexibleWidth, .flexibleHeight]

// layout code...
}

Here's how it looks added to a view controller in IB:

Sample Image

and here's how it looks via code:

class AxesTestViewController: UIViewController {

var axesView = AxesView()

override func viewDidLoad() {
super.viewDidLoad()
axesView.frame = self.view.bounds
axesView.bgColor = .blue
axesView.lineColor = .red
self.view.addSubview(axesView)
}

}

Sample Image


EDIT

After discussion in comments, here is one approach to this @IBDesignable xib with "draggable" cross-hair-lines.

This uses constraints and modifies the .constant on centerX and centerY to move the lines. I also moved your touches... funcs inside the custom view to keep things a bit more orderly.

Complete example code follows, including the NibLoadable code (I renamed your control to TapAxesView for comparison):

public protocol NibLoadable {
static var nibName: String { get }
}

public extension NibLoadable where Self: UIView {

static var nibName: String {
return String(describing: Self.self) // defaults to the name of the class implementing this protocol.
}

static var nib: UINib {
let bundle = Bundle(for: Self.self)
return UINib(nibName: Self.nibName, bundle: bundle)
}

func setupFromNib() {
guard let view = Self.nib.instantiate(withOwner: self, options: nil).first as? UIView else { fatalError("Error loading \(self) from nib") }
addSubview(view)
view.backgroundColor = .clear
view.frame = self.bounds
view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
}
}

@IBDesignable
class TapAxesView: UIView, NibLoadable {

@IBOutlet var hLine: UIView!
@IBOutlet var vLine: UIView!

@IBOutlet var vLineCenterX: NSLayoutConstraint!
@IBOutlet var hLineCenterY: NSLayoutConstraint!

@IBInspectable var bgColor: UIColor = UIColor.white {
didSet {
self.backgroundColor = bgColor
}
}

@IBInspectable var lineColor: UIColor = UIColor.cyan {
didSet {
hLine.backgroundColor = lineColor
vLine.backgroundColor = lineColor
}
}

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

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

func updateCenter(_ point: CGPoint) -> Void {
// prevent centers from moving outside the bounds
let halfW = (bounds.size.width / 2.0)
let halfH = (bounds.size.height / 2.0)
let x = point.x - halfW
let y = point.y - halfH
vLineCenterX.constant = min(max(x, -halfW), halfW)
hLineCenterY.constant = min(max(y, -halfH), halfH)
}

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first! as UITouch
let p = touch.location(in: self)
updateCenter(p)
}

override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first! as UITouch
let p = touch.location(in: self)
updateCenter(p)
}

override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
touchesMoved(touches, with: event)
}

}

class TapAxesTestViewController: UIViewController {

var axesView = TapAxesView()

override func viewDidLoad() {
super.viewDidLoad()
axesView.frame = self.view.bounds

axesView.bgColor = .clear
axesView.lineColor = .red
self.view.addSubview(axesView)

axesView.translatesAutoresizingMaskIntoConstraints = false

axesView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 0).isActive = true
axesView.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 0).isActive = true
axesView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: 0).isActive = true
axesView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: 0).isActive = true
}

}

and here is the source for the TapAxesView.xib file:

<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15705" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15706"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="TapAxesView" customModule="MiniScratch" customModuleProvider="target">
<connections>
<outlet property="hLine" destination="wry-9o-V8F" id="uCc-eL-sSS"/>
<outlet property="hLineCenterY" destination="Txd-hz-fX2" id="OUH-HO-ghG"/>
<outlet property="vLine" destination="x0E-M7-ETl" id="BaY-4q-4RA"/>
<outlet property="vLineCenterX" destination="pAM-XU-BDo" id="fgf-lE-dn3"/>
</connections>
</placeholder>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="iN0-l3-epB">
<rect key="frame" x="0.0" y="0.0" width="375" height="382"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="wry-9o-V8F">
<rect key="frame" x="0.0" y="189" width="375" height="4"/>
<color key="backgroundColor" white="0.66666666669999997" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="height" constant="4" id="OqP-vn-hAj"/>
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="x0E-M7-ETl">
<rect key="frame" x="185.5" y="0.0" width="4" height="382"/>
<color key="backgroundColor" white="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="width" constant="4" id="cqZ-JL-4vH"/>
</constraints>
</view>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="wry-9o-V8F" firstAttribute="centerY" secondItem="iN0-l3-epB" secondAttribute="centerY" id="Txd-hz-fX2"/>
<constraint firstItem="wry-9o-V8F" firstAttribute="trailing" secondItem="vUN-kp-3ea" secondAttribute="trailing" id="bcB-iZ-vbV"/>
<constraint firstItem="vUN-kp-3ea" firstAttribute="bottom" secondItem="x0E-M7-ETl" secondAttribute="bottom" id="hfl-4K-VZq"/>
<constraint firstItem="wry-9o-V8F" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" id="ma5-u8-0U4"/>
<constraint firstItem="x0E-M7-ETl" firstAttribute="centerX" secondItem="iN0-l3-epB" secondAttribute="centerX" id="pAM-XU-BDo"/>
<constraint firstItem="x0E-M7-ETl" firstAttribute="top" secondItem="vUN-kp-3ea" secondAttribute="top" id="z8W-cZ-2Bi"/>
</constraints>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
<point key="canvasLocation" x="138.40000000000001" y="24.287856071964018"/>
</view>
</objects>
</document>

EDIT 2

Maybe worth trying... a custom @IBDesignable view via code only ... no xib file (or nib-loading) needed. Also, this uses CALayer for the "cross-hair-lines" instead of subviews. Makes it a little "lighter weight."

@IBDesignable
class LayerAxesView: UIView {

var hLine: CALayer = CALayer()
var vLine: CALayer = CALayer()

var curX: CGFloat = -1.0
var curY: CGFloat = -1.0

let lineWidth: CGFloat = 4.0

@IBInspectable var bgColor: UIColor = UIColor.white {
didSet {
self.backgroundColor = bgColor
}
}

@IBInspectable var lineColor: UIColor = UIColor.cyan {
didSet {
hLine.backgroundColor = lineColor.cgColor
vLine.backgroundColor = lineColor.cgColor
}
}

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

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

override func prepareForInterfaceBuilder() {
commonInit()
}

func commonInit() -> Void {
if hLine.superlayer == nil {
layer.addSublayer(hLine)
layer.addSublayer(vLine)
hLine.backgroundColor = lineColor.cgColor
vLine.backgroundColor = lineColor.cgColor
backgroundColor = bgColor
}
}

override func layoutSubviews() {
// if curX and curY have not yet been set,
// such as on init or when used in Storyboard / IB,
// initialize to center of view
if curX == -1 {
curX = bounds.midX
curY = bounds.midY
}
hLine.frame = CGRect(x: bounds.minX, y: curY - lineWidth * 0.5, width: bounds.maxX, height: lineWidth)
vLine.frame = CGRect(x: curX - lineWidth * 0.5, y: bounds.minY, width: lineWidth, height: bounds.maxY)
}

func updateCenter(_ point: CGPoint) -> Void {
// prevent centers from moving outside the bounds
curX = max(min(bounds.maxX, point.x), bounds.minX)
curY = max(min(bounds.maxY, point.y), bounds.minY)
// disable CALayer's built-in animation
CATransaction.begin()
CATransaction.setDisableActions(true)
setNeedsLayout()
layoutIfNeeded()
CATransaction.commit()
}

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first! as UITouch
let p = touch.location(in: self)
updateCenter(p)
}

override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first! as UITouch
let p = touch.location(in: self)
updateCenter(p)
}

override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
touchesMoved(touches, with: event)
}

}

class TapAxesTestViewController: UIViewController {

var axesView: LayerAxesView = {
let v = LayerAxesView()
v.translatesAutoresizingMaskIntoConstraints = false
v.lineColor = .red
v.bgColor = UIColor.blue.withAlphaComponent(0.5)
return v
}()

override func viewDidLoad() {
super.viewDidLoad()

self.view.addSubview(axesView)

// respect safe area
let g = view.safeAreaLayoutGuide

// constrain axesView to safe area with 40-pts "padding"
NSLayoutConstraint.activate([
axesView.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
axesView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 40.0),
axesView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -40.0),
axesView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -40.0),
])
}

}

IBDesignable drawings not displaying in storyboard

This finally solved my problem:
https://stackoverflow.com/a/26720740/5291486

To sum up, I enabled Editor > Automatically Refresh Views and then clicked Editor > Refresh All Views.



Related Topics



Leave a reply



Submit