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).
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:
Clicking "Show" to me to the warnings:
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":
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):
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
.
Change it's custom class to your
IBDesignable
subclass:Set the
IBInspectable
properties:
IBInspectable variable value is not changing in interface builder and simulator
A couple of thoughts:
As Daniel said, you really want to call
drawParallelgram
in youroffset
observer.Also, in
layoutSubviews
, you’re updating theframe
of your shape layer. You want to reset itspath
and update your mask again.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.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 theinit
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 ininit
methods.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 duringinit
, I generally write twoinit
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:
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:
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)
}
}
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
How to Add Image in Uitableviewrowaction
Open Mobile Safari from a Link in a Webview
iOS Swift Call Web Service Using Soap
How to Render a Whole Uitableview as an Uiimage in iOS
Autolayout Link Two Uilabels to Have the Same Font Size
Remove Single Remote Notification from Notification Center
How to Debug an iOS Extension (.Appex)
How to Apply a Vignette Cifilter to a Live Camera Feed in iOS
Uipangesturerecognizer - Only Vertical or Horizontal
Xcode 6 Isn't Autocompleting in Swift
Adjust Uibutton Font Size to Width
Ios4: How to Use Video File as an Opengl Texture
Swift iOS Check If Remote Push Notifications Are Enabled in iOS9 and iOS10
How to Set a Uitableview to Grouped Style
How to Set Kerning in iPhone Uilabel
How to Authenticate the Gklocalplayer on My 'Third Party Server'
Urlsession.Datatask with Request Block Not Called in Background