EXC_BAD_ACCESS on a non-nil UIView - swift

EDIT: This appears to be a Swift or Cocoapods bug. The LinePlot is defined in a Cocoapod. When I inject my framework code directly into my project, everything is normal.
EDIT 2: At configure, my plotPoints array only has one value(seen in debugger)! What could be happening? Again, copy-pasting my framework code and using that new object works fine, my array has its values.
I get a bad access when calling a method on a custom UIView (LinePlot). The view is presented O.K. and is allocated O.K., but calling this method produces the error.
I have tried using the debugger and examining the tableViewCell's instance data.
let customView = LinePlot()
public func configure<ViewDataModel>(model withViewData: ViewDataModel) {
guard let viewData = withViewData as? StockPlotViewData else {
fatalError("Wrong type sent to StockPlotCell")
}
contentView.addSubview(customView)
backgroundColor = .blue
setupCustomView()
let plotPoints: [CGPoint] = viewData.points.map { (doublePoint) -> CGPoint in
let (x,y) = doublePoint
return CGPoint(x: x, y: y)
}
customView.configure(withViewData: plotPoints, fillColor: .clear, graphLineWidth: 5.0)
}
I expect the 'LinePlot' to be allocated and its methods able to be safely called. However, I get the following error:
Thread 1: EXC_BAD_ACCESS (code=2, address=0x10c35e750)
on line
customView.configure(withViewData: plotPoints, fillColor: .clear, graphLineWidth: 5.0)

Related

Drawing directly in a NSView without using the draw(_ updateRect: NSRect) function

I would like to draw CGImage pictures directly to a View and with the normal method using the draw func I only get 7 pictures in a second on a new Mac Book Pro. So I decided to use the updateLayer func instead. I have defined wantsUpdateLayer = true and my new updateLayer func is called as expected. But then starts my problem. When using the draw func, I get the current CGContext with "NSGraphicsContext.current?.cgContext" but in my updateLayer func the "NSGraphicsContext.current?.cgContext" is nil. So I do not know where to put my CGImage, that it will be displayed on my screen. Also "self.view?.window?.graphicsContext?.cgContext" and "self.window?.graphicsContext?.cgContext" are nil, too. There are no buttons or other elements in this view and in the window of the view, only one picture, filling the complete window. And this picture must change 30 times in a second. Generating the pictures is done by a separate thread and needs about 1 millisecond for a picture. I think that from "outside" the NSView class it is not possible to write the picture but my updateLayer func is inside the class.
Here is what the func looks like actually:
override func updateLayer ()
{
let updateRect: NSRect = NSRect(x: 0.0, y: 0.0, width: 1120.0, height: 768.0)
let context1 = self.view?.window?.graphicsContext?.cgContext
let context2 = self.window?.graphicsContext?.cgContext
let context3 = NSGraphicsContext.current?.cgContext
}
And all three contexts are nil in the time the function is called automatically after I set the needsDisplay flag.
Any ideas where to draw my CGImages?
The updateLayer func is called automatically by the user interface. I do not call it manually. It is called by the view. My problem is where inside this method to put my picture to be shown on the screen. Perhaps I have to add a layer or use a default layer of the view but I do not know how to do this.
Meanwhile I have found the solution with some tipps from a good friend:
override var wantsUpdateLayer: Bool
{
return (true)
}
override func updateLayer ()
{
let cgimage: CGImage? = picture // Here comes the picture
if cgimage != nil
{
let nsimage: NSImage? = NSImage(cgImage: cgimage!, size: NSZeroSize)
if nsimage != nil
{
let desiredScaleFactor: CGFloat? = self.window?.backingScaleFactor
if desiredScaleFactor != nil
{
let actualScaleFactor: CGFloat? = nsimage!.recommendedLayerContentsScale(desiredScaleFactor!)
if actualScaleFactor != nil
{
self.layer!.contents = nsimage!.layerContents(forContentsScale: actualScaleFactor!)
self.layer!.contentsScale = actualScaleFactor!
}
}
}
}
}
This is the way to directly write into the layer. Depending on the format (CGImage or NSImage) you first must convert it. As soon as the func wantsUpdateLayer returns a true, the func updateLayer() is used instead of the func draw(). Thats all.
For all who want to see my "Normal" draw function:
override func draw (_ updateRect: NSRect)
{
let cgimage: CGImage? = picture // Here comes the picture
if cgimage != nil
{
if #available(macOS 10.10, *)
{
NSGraphicsContext.current?.cgContext.draw(cgimage!, in: updateRect)
}
}
else
{
super.draw(updateRect)
}
}
The additional speed is 2 times or more, depending on what hardware you use. On a modern Mac Pro there is only a little bit more speed but on a modern Mac Book Pro you will get 10 times or more speed. This works with Mojave 10.14.6 and Catalina 10.15.6. I did not test it with older macOS versions. The "Normal" draw function works with 10.10.6 to 10.15.6.

Enabling Swift compiler optimization causes EXC_BAD_ACCESS crash in custom view controller transition

For replacing the default animation when pushing / popping a view controller onto the navigation stack with a cross dissolve animation I have implemented a custom view controller transition that conforms to the protocol UIViewControllerAnimatedTransitioning.
Here's my implementation of the animation function:
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let fromViewController = transitionContext.viewController(forKey: .from),
let toViewController = transitionContext.viewController(forKey: .to) else {
return
}
let animationView = transitionContext.containerView
animationView.addSubview(toViewController.view) // đź’Ą
let duration = transitionDuration(using: transitionContext)
fade(from: fromViewController.view,
to: toViewController.view,
duration: duration,
completion: {
let didComplete = !transitionContext.transitionWasCancelled
transitionContext.completeTransition(didComplete)
})
}
When running the app with my Debug configuration in works like a charm. However, when running it with my Release configuration it crashes in the line marked with đź’Ą.
The difference that causes the crash is the Swift compiler optimization level which is defined as follows:
Debug: None [-Onone]
Release: Fast, Whole Module Optimization [-O -whole-module-optimization]
I really think that the whole module optimization is a pretty good idea for our release. But I don't see which variable could possibly be released here and using print statements I've even checked that all those variables used in this line are properly set, i.e. the animationView and the toViewController.view. So what on earth is going on here? What is the "optimization" that causes the crash?

Detected missing constraints for NSTextView

I have created a Document-based application that should save an NSAttributedString with a image into a package. I ran the application and added a image to the text view and saved it. When I opened the file, a dialog box said "the document 'x' could not be opened" and this was printed to the console:
[Layout] Detected missing constraints for <NSTextField: 0x100b3f480>. It cannot be placed because there are not enough constraints to fully
define the size and origin. Add the missing constraints, or set
translatesAutoresizingMaskIntoConstraints=YES and constraints will be generated for you. If this view is laid out manually on macOS 10.12 and later,
you may choose to not call [super layout] from your override. Set a breakpoint on DETECTED_MISSING_CONSTRAINTS to debug. This error will only be logged once.
Document.swift:
enum CookRecipesFileNames : String {
case notes = "Notes.rtfd"
}
class Document: NSDocument {
var documentFileWrapper = FileWrapper(directoryWithFileWrappers: [:])
var popover : NSPopover?
var notes : NSAttributedString = NSAttributedString()
...
override class func autosavesInPlace() -> Bool {
return true
}
override func fileWrapper(ofType typeName: String) throws -> FileWrapper {
let notesRTFdata = try self.notes.data(from: NSRange(0..<self.notes.length), documentAttributes: [NSDocumentTypeDocumentAttribute: NSRTFDTextDocumentType])
if let oldTextFileWrapper = self.documentFileWrapper.fileWrappers?[CookRecipesFileNames.notes.rawValue] {
self.documentFileWrapper.removeFileWrapper(oldTextFileWrapper)
}
self.documentFileWrapper.addRegularFile(withContents: notesRTFdata, preferredFilename: CookRecipesFileNames.notes.rawValue)
return self.documentFileWrapper
}
override func read(from fileWrapper: FileWrapper, ofType typeName: String) throws {
guard let documentNotesData = fileWrappers[CookRecipesFileNames.notes.rawValue]?.regularFileContents else {
throw err(.cannotLoadNotes)
}
guard let documentNotes = NSAttributedString(rtfd: documentNotesData, documentAttributes: nil) else {
throw err(.cannotLoadNotes)
}
self.documentFileWrapper = fileWrapper
self.notes = documentNotes
}
}
Any help would be appreciated!
The warning 'Detected missing constraints...' is a Build Warning, telling you that you have added constraints to a view, but not enough to determine x and y coords, and height and width for the view. If you add no constraints in IB (an option here is to remove them all), Xcode will tell the app to use the exact position and dimensions used in IB. If you add ANY constraints to a view, you must add enough to determine both coordinates and both dimensions. If you want to keep your constraints (this is the second option), go to IB, and look for the yellow or red warning error, at the top left:
This will give you a list of missing and conflicting restraints in the View Controller.

Swift 3 - What does this mean?

I converted my project to Swift 3, I am having trouble with the following piece of code, it seems that this ( >>>-) is no longer used in Swift 3. What does >>>- actually mean? and how to use it in Swift 3?
fileprivate func addImageToView(_ view: UIView, image: UIImage?) -> UIImageView? {
guard let image = image else { return nil }
let imageView = Init(UIImageView(image: image)) {
$0.translatesAutoresizingMaskIntoConstraints = false
$0.alpha = 0
}
view.addSubview(imageView)
// add constraints
[NSLayoutAttribute.left, .right, .top, .bottom].forEach { attribute in
(view, imageView) >>>- { $0.attribute = attribute }
}
imageView.layoutIfNeeded()
return imageView
}
The >>>- returns a value. Before Swift 3, the compiler did not warn you if you did not assign the return value of a function or method to anything. Starting in Swift 3, you will get an error if the return value of a function or method (including operators) is unused. A library author can fix this by adding the #discardableResult annotation, but in the meantime you will have to change that line of code to:
let _ = (view, imageView) >>>- { $0.attribute = attribute }
Despite the fact this answer can solve the issue in your side, there still a need to the author of the library to make some changes on his side.
The problem is because swift removed completely the var from function parameters and somehow that change is affecting the param you're receiving in the closure. (It seem a bug to me). You need to figure it out what is the type of the inout param the library is passing to the operator closure, and declare it as inout in your call. let's say that type is Constraint (this might not be the same as the library), then on you:
(view, imageView) >>>- { (i : inout Constraint) in
i.attribute = attribute
}
But again, the author might still need to make some changes in the operator implementation as well.

Swift generic function not working

So, I made a generic function in a structure which has some static methods for helping to create customized UIButtons and so on. So I did this code:
static func createAlertPicker<T: UIViewController where T: UIPickerViewDelegate, T: UIPickerViewDataSource>(#title: String, inout forPicker picker: UIPickerView, viewController: T) -> UIAlertController {
let alert = UIAlertController(title: title, message: "\n\n\n\n\n\n\n\n\n\n", preferredStyle: .Alert)
alert.view.tintColor = data.backgroundColor
picker = createPickerViewWithFrame(CGRectZero, delegate: viewController, dataSource: viewController, backgroundColor: UIColor.clearColor(), addToView: alert.view)
picker.frame = CGRect(x: 2, y: 70, width: 266, height: 162)
return alert
}
I don't get an error doing this but when calling this method in a ViewController like so:
let alert = CreatorClass.createAlertPicker(title: "select sortage", forPicker: &self.pickerView!, viewController: self)
I get a crazy error telling me Type 'UIViewController' does not conform to protocol 'UIPickerViewDelegate'. Although it is implemented, that's the ViewController's declaration:
class PlayerDetails:UIViewController, UITableViewDataSource, UITableViewDelegate, UIPickerViewDelegate, UIPickerViewDataSource
By the way, this problem only occurs in one ViewController, I'm calling it several times. Maybe it should be mentioned that this line of code (let alert = ...) is not even compiled in the first place.
I really don't understand this. Thank for any help ! :]
Yeah that error is a total lie.
The problem is with the middle parameter: &self.pickerView!.
self.pickerView is an optional. Your optional contains a reference, but unwrapping that reference passes back you back a fresh copy of the reference by value. You don’t get access to the original reference inside the optional. So when you call !, you get an immutable value. You can’t change it or assign to it, and that means you can’t pass it as an inout parameter.
Here’s a simpler example:
let i: Int? = 5
func f(inout i: Int) { i = 6 }
f(&i!) // error: 'Int' is not convertible to '#lvalue inout $T3’
This is the compiler saving you from a potentially very confusing runtime bug – if the value were passed in and changed, it would make no difference to the value you actually intended to change. Only the temporary copy would have been changed.
It might be a bit confusing because classes are reference types so you’re not used to thinking about them in value terms. But references themselves are values. What you are getting out of the unwrap is a copy of the reference, not a copy of the thing referred to.
If you change your call to something like the following, it should work:
if var picker = self.pickerView {
let alert = CreatorClass.createAlertPicker(title: "select sortage", forPicker: &picker, viewController: self)
// don’t forget to assign the value back...
self.pickerView = picker
}
This version also has the benefit of not exploding in flames if you’ve ever forgotten to set forPicker to be a value before you force unwrap it.
But if all you are using the inout for is to return a new picker (doesn’t look in your createAlertPicker like you use the value passed in, only assign to it), then why not ditch the inout and make the function return a pair of values:
static func createAlertPicker
<T: UIViewController where T: UIPickerViewDelegate>
(#title: String, viewController: T)
// return a tuple
-> (UIPickerView,UIAlertController) {
// etc…
var picker = createPickerViewWithFrame(CGRectZero, delegate: viewController, dataSource: viewController, backgroundColor: UIColor.clearColor(), addToView: alert.view)
picker.frame = CGRect(x: 2, y: 70, width: 266, height: 162)
return (picker, alert)
}
let (picker, alert) = CreatorClass.createAlertPicker(title: "select sortage”, viewController: self)
self.pickerView = picker