Exc_Bad_Access Using Ibinspectable

EXC_BAD_ACCESS Using IBInspectable

You have an infinite recursion there, that is causing the crash. Basically within the setter of borderColor you're calling the setter for the same property, resulting the infinite recursion.

This happens because class extensions are not allowed to have stored properties, so Swift doesn't generate a backstore for your property, instead it treats it like a computed property, and calls the setter whenever you try to set the property.

There are two solutions that I can think of at this time, that will solve your problem:

  1. Subclass UIView, add the two properties there, update the class in IB to match the name of your new class.
  2. Use associated objects in your UIView accessors (objc_setAssociatedObject()/ objc_getAssociatedObject()) instead of direct iVar reference. You will not need to subclass and to update your xibs, however this solution is a little bit messier than the first one.

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),
])
}

}

Don't understand how to fix Thread 1: ECX_BAD_ACCESS (code = EXC_I386_GPFLT) (line chart swift iOS)

In storyboard remove reference outlet link to 'lineChart' and try this:

import UIKit

extension String {
func size(withSystemFontSize pointSize: CGFloat) -> CGSize {
return (self as NSString).size(attributes: [NSFontAttributeName: UIFont.systemFont(ofSize: pointSize)])
}
}

extension CGPoint {
func adding(x: CGFloat) -> CGPoint { return CGPoint(x: self.x + x, y: self.y) }
func adding(y: CGFloat) -> CGPoint { return CGPoint(x: self.x, y: self.y + y) }
}

class ViewController: UIViewController {

// @IBOutlet var lineChart: LineChart! ////////////REMOVED THIS
var lineChart = LineChart(frame: CGRect.zero) ////////////ADDED THIS

override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.

let f: (CGFloat) -> CGPoint = {
let noiseY = (CGFloat(arc4random_uniform(2)) * 2 - 1) * CGFloat(arc4random_uniform(4))
let noiseX = (CGFloat(arc4random_uniform(2)) * 2 - 1) * CGFloat(arc4random_uniform(4))
let b: CGFloat = 5
let y = 2 * $0 + b + noiseY
return CGPoint(x: $0 + noiseX, y: y)
}

let xs = [Int](1..<20)

let points = xs.map({f(CGFloat($0 * 10))})

////////////ADDED THIS
self.lineChart.frame = CGRect(x: 0, y: 0, width: self.view.frame.width, height: self.view.frame.height)
self.view.addSubview(self.lineChart)

lineChart.deltaX = 20
lineChart.deltaY = 30

lineChart.plot(points)

}

override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}

class LineChart: UIView {

let lineLayer = CAShapeLayer()
let circlesLayer = CAShapeLayer()

var chartTransform: CGAffineTransform?

@IBInspectable var lineColor: UIColor = UIColor.green {
didSet {
lineLayer.strokeColor = lineColor.cgColor
}
}

@IBInspectable var lineWidth: CGFloat = 1

@IBInspectable var showPoints: Bool = true { // show the circles on each data point
didSet {
circlesLayer.isHidden = !showPoints
}
}

@IBInspectable var circleColor: UIColor = UIColor.green {
didSet {
circlesLayer.fillColor = circleColor.cgColor
}
}

@IBInspectable var circleSizeMultiplier: CGFloat = 3

@IBInspectable var axisColor: UIColor = UIColor.white
@IBInspectable var showInnerLines: Bool = true
@IBInspectable var labelFontSize: CGFloat = 10

var axisLineWidth: CGFloat = 1
var deltaX: CGFloat = 10 // The change between each tick on the x axis
var deltaY: CGFloat = 10 // and y axis
var xMax: CGFloat = 100
var yMax: CGFloat = 100
var xMin: CGFloat = 0
var yMin: CGFloat = 0

var data: [CGPoint]?

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

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

func combinedInit() {
layer.addSublayer(lineLayer)
lineLayer.fillColor = UIColor.clear.cgColor
lineLayer.strokeColor = lineColor.cgColor

layer.addSublayer(circlesLayer)
circlesLayer.fillColor = circleColor.cgColor

layer.borderWidth = 1
layer.borderColor = axisColor.cgColor
}

override func layoutSubviews() {
super.layoutSubviews()
lineLayer.frame = bounds
circlesLayer.frame = bounds

if let d = data{
setTransform(minX: xMin, maxX: xMax, minY: yMin, maxY: yMax)
plot(d)
}
}

func setAxisRange(forPoints points: [CGPoint]) {
guard !points.isEmpty else { return }

let xs = points.map() { $0.x }
let ys = points.map() { $0.y }

// МИНИМАЛЬНЫЕ И МАКСИМАЛЬНЫЕ ЗНАЧЕНИЯ
xMax = ceil(xs.max()! / deltaX) * deltaX
yMax = ceil(ys.max()! / deltaY) * deltaY
xMin = 0
yMin = 0
setTransform(minX: xMin, maxX: xMax, minY: yMin, maxY: yMax)
}

func setAxisRange(xMin: CGFloat, xMax: CGFloat, yMin: CGFloat, yMax: CGFloat) {
self.xMin = xMin
self.xMax = xMax
self.yMin = yMin
self.yMax = yMax

setTransform(minX: xMin, maxX: xMax, minY: yMin, maxY: yMax)
}

func setTransform(minX: CGFloat, maxX: CGFloat, minY: CGFloat, maxY: CGFloat) {

let xLabelSize = "\(Int(maxX))".size(withSystemFontSize: labelFontSize)

let yLabelSize = "\(Int(maxY))".size(withSystemFontSize: labelFontSize)

let xOffset = xLabelSize.height + 2
let yOffset = yLabelSize.width + 5

let xScale = (bounds.width - yOffset - xLabelSize.width/2 - 2)/(maxX - minX)
let yScale = (bounds.height - xOffset - yLabelSize.height/2 - 2)/(maxY - minY)

chartTransform = CGAffineTransform(a: xScale, b: 0, c: 0, d: -yScale, tx: yOffset, ty: bounds.height - xOffset)

setNeedsDisplay()
}

override func draw(_ rect: CGRect) {
// draw rect comes with a drawing context, so lets grab it.
// Also, if there is not yet a chart transform, we will bail on performing any other drawing.
// I like guard statements for this because it's kind of like a bouncer to a bar.
// If you don't have your transform yet, you can't enter drawAxes.
guard let context = UIGraphicsGetCurrentContext(), let t = chartTransform else { return }
drawAxes(in: context, usingTransform: t)
}

func drawAxes(in context: CGContext, usingTransform t: CGAffineTransform) {
context.saveGState()

// make two paths, one for thick lines, one for thin
let thickerLines = CGMutablePath()
let thinnerLines = CGMutablePath()

// the two line chart axes
let xAxisPoints = [CGPoint(x: xMin, y: 0), CGPoint(x: xMax, y: 0)]
let yAxisPoints = [CGPoint(x: 0, y: yMin), CGPoint(x: 0, y: yMax)]

// add each to thicker lines but apply our transform too.
thickerLines.addLines(between: xAxisPoints, transform: t)
thickerLines.addLines(between: yAxisPoints, transform: t)

// next we go from xMin to xMax by deltaX using stride
for x in stride(from: xMin, through: xMax, by: deltaX) {

// tick points are the points for the ticks on each axis
// we check showInnerLines first to see if we are drawing small ticks or full lines
// tip for new guys: `let a = someBool ? b : c` is called a ternary operator
// in english it means "let a = b if somebool is true, or c if it is false."

let tickPoints = showInnerLines ?
[CGPoint(x: x, y: yMin).applying(t), CGPoint(x: x, y: yMax).applying(t)] :
[CGPoint(x: x, y: 0).applying(t), CGPoint(x: x, y: 0).applying(t).adding(y: -5)]

thinnerLines.addLines(between: tickPoints)

if x != xMin { // draw the tick label (it is too buy if you draw it at the origin for both x & y
let label = "\(Int(x))" as NSString // Int to get rid of the decimal, NSString to draw
let labelSize = "\(Int(x))".size(withSystemFontSize: labelFontSize)
let labelDrawPoint = CGPoint(x: x, y: 0).applying(t)
.adding(x: -labelSize.width/2)
.adding(y: 1)

label.draw(at: labelDrawPoint,
withAttributes:
[NSFontAttributeName: UIFont.systemFont(ofSize: labelFontSize),
NSForegroundColorAttributeName: axisColor])
}
}
// repeat for y
for y in stride(from: yMin, through: yMax, by: deltaY) {

let tickPoints = showInnerLines ?
[CGPoint(x: xMin, y: y).applying(t), CGPoint(x: xMax, y: y).applying(t)] :
[CGPoint(x: 0, y: y).applying(t), CGPoint(x: 0, y: y).applying(t).adding(x: 5)]

thinnerLines.addLines(between: tickPoints)

if y != yMin {
let label = "\(Int(y))" as NSString
let labelSize = "\(Int(y))".size(withSystemFontSize: labelFontSize)
let labelDrawPoint = CGPoint(x: 0, y: y).applying(t)
.adding(x: -labelSize.width - 1)
.adding(y: -labelSize.height/2)

label.draw(at: labelDrawPoint,
withAttributes:
[NSFontAttributeName: UIFont.systemFont(ofSize: labelFontSize),
NSForegroundColorAttributeName: axisColor])
}
}
// finally set stroke color & line width then stroke thick lines, repeat for thin
context.setStrokeColor(axisColor.cgColor)
context.setLineWidth(axisLineWidth)
context.addPath(thickerLines)
context.strokePath()

context.setStrokeColor(axisColor.withAlphaComponent(0.5).cgColor)
context.setLineWidth(axisLineWidth/2)
context.addPath(thinnerLines)
context.strokePath()

context.restoreGState()
// whenever you change a graphics context you should save it prior and restore it after
// if we were using a context other than draw(rect) we would have to also end the graphics context
}

func plot(_ points: [CGPoint]) {
lineLayer.path = nil
circlesLayer.path = nil
data = nil

guard !points.isEmpty else { return }

self.data = points

if self.chartTransform == nil {
setAxisRange(forPoints: points)
}

let linePath = CGMutablePath()
linePath.addLines(between: points, transform: chartTransform!)

lineLayer.path = linePath

if showPoints {
circlesLayer.path = circles(atPoints: points, withTransform: chartTransform!)
}
}

func circles(atPoints points: [CGPoint], withTransform t: CGAffineTransform) -> CGPath {

let path = CGMutablePath()
let radius = lineLayer.lineWidth * circleSizeMultiplier/2
for i in points {
let p = i.applying(t)
let rect = CGRect(x: p.x - radius, y: p.y - radius, width: radius * 2, height: radius * 2)
path.addEllipse(in: rect)

}

return path
}
} // <- I didn't close the LineChart class up top, closing it now

}

Why does using [UIColor colorWithRed:gred:blue:alpha] lead to EXC_BAD_ACCESS?

When you use [UIColor blueColor], you don't create the object; you get a reference to it and something else manages its life cycle.

When you init a new UIColor object, you're responsible for making sure it is still valid when used. "assign" doesn't increase the object's reference count; "strong" does.

NSNotification EXC_BAD_ACCESS

Try the code below, it should be okay:

NSDictionary * userInfo = [NSDictionary dictionaryWithObjectsAndKeys:..., nil];
[[NSNotificationCenter defaultCenter] postNotificationName:AsyncImageLoadDidFinish
object:target
userInfo:userInfo];

or:

NSDictionary * userInfo = [[NSDictionary alloc] initWithObjectsAndKeys:..., nil];
[[NSNotificationCenter defaultCenter] postNotificationName:AsyncImageLoadDidFinish
object:target
userInfo:userInfo];
[userInfo release];

How to get IBInspectable var value with get return?

Why do you even redefined getter?

It looks like you just need to call refreshStars() after value is set, so just work with willSet/didSet

@IBInspectable var commingId: Int {
didSet {
refreshStars()
}
}


Related Topics



Leave a reply



Submit