Anchor Constraints not Honored in Xcode 10 / iOS 12 - swift

This all worked yesterday under Xcode 9 and iOS 11; however, after updating to Xcode 10 and iOS 12, the view is no longer showing. I was having a video displayed inside a view. Today I could hear but not see the video. I checked the frame and found it to be zero which explained the issue. However, nothing had changed from the prior version. I have removed the video stuff and have just looked at the view's frame after anchor constraints were applied and they are all zero.
import UIKit
import AVKit
class VideoView: UIView {
private var videoURL:URL!
private var parentView:UIView!
private var avPlayer:AVPlayer!
private var avPlayerLayer:AVPlayerLayer!
init(url:URL, parentView:UIView) {
super.init(frame: .zero)
self.videoURL = url
self.parentView = parentView
setup()
}
private func setup() {
self.translatesAutoresizingMaskIntoConstraints = false
self.parentView.addSubview(self)
self.topAnchor.constraint(equalTo: self.parentView.safeAreaLayoutGuide.topAnchor, constant: 10).isActive = true
self.leadingAnchor.constraint(equalTo: self.parentView.leadingAnchor, constant: 8).isActive = true
self.trailingAnchor.constraint(equalTo: self.parentView.trailingAnchor, constant: -8).isActive = true
self.heightAnchor.constraint(equalToConstant: 200).isActive = true
print(self.parentView.frame)
print(self.frame)
}
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
notice the two print statements at the end of setup(). The parentView return 0,0,414,736. The self view returns 0,0,0,0.
If in Init I set the frame size it will respect it; however, it does not apply the anchor constraints so the size of the view will remain whatever it is I put into the init.
It appears that the anchor constraints are not at all being taken into account. There is no error in the debugger about them and as we can see, translatesAutoresizingMaskIntoConstraints is set to false and all constraints have the isActive set to true.
Only the Xcode and iOS version have changed. What am I missing?
Update 1:
If I create a label and add it to self, the label shows fine. If I create a background color for self that also shows fine including the proper height as set with the anchor constraints. However, the frame remains at zero. So when trying to add an AVPlayerLayer by setting its frame to the bounds of self it doesn't work because of course self remains at zero. So the question remains as to why the frame's dimensions are not changing after initialization.
Update 2:
I added a self.layoutIfNeeded() just after applying the anchor constraints and that seems to have solved the issue. Although looking at the frame for self and I get -199,-100,398,200 Cannot say I understand the values for X and Y. Nevertheless the layOutIfNeeded seems to have solved the problem although why this is required in Xcode 10/ iOS 12 is also a mystery.

I posted what seems to be the answer in my second update, but for clarity:
I added a self.layoutIfNeeded() just after applying the anchor constraints and that seems to have solved the issue. Although looking at the frame for self and I get -199,-100,398,200 Cannot say I understand the values for X and Y. Nevertheless the layOutIfNeeded seems to have solved the problem although why this is required in Xcode 10/ iOS 12 is also a mystery.

Related

Scrollable NSTextView with custom NSTextStorage for formatting

I'm trying to make a text editor with formatting for Mac OS. Which I have working using an NSTextView together with a custom NSTextStorage class. Which applies attributes like bold etc to NSAttributableStrings.
This all seems to work fine as seen in screenshot one below. Which is an NSTextView with a custom NSTextStorage class attached to it. Which applies the formatting through attributes on an NSAttributeableString
However, having everything the same, but getting a scrollable NSTextView from the Apple supplied function NSTextView.scrollableTextView() it does not display any text at all. Even though you can see in the screenshot that the NStextView is actually visible. Also, moving my mouse over the editor changes the cursor to the editor cursor. But I can't select, type or do anything.
Doing the exact same thing as above, but not supplying a text container to the text view does show that it is wired up correctly, since I do get a scrollable text view then. Where the scrolling actually works, but then of course the formatting is no longer applied.
So I'm confused on what I have to do now.
This is basically my setup:
//
// TextDocumentViewController.swift
//
// Created by Matthijn on 15/02/2022.
// Based on LayoutWithTextKit2 Sample from Apple
import Foundation
import AppKit
class TextDocumentViewController: NSViewController {
// Extends NSTextStorage, applies attributes to NSAttributeAbleString for formatting
private var textStorage: TextStorage
// Not custom yet, default implementation - do I need to subclass this specifically and implement something to support the scrolling behaviour? Which seems weird to me since it does work without scrolling support
private var layoutManager: NSLayoutManager
// Also default implementation
private var textContainer: NSTextContainer
private var textDocumentView: NSTextView
private var scrollView: NSScrollView
required init(content: String) {
textStorage = TextStorage(editorAttributes: MarkdownAttributes)
layoutManager = NSLayoutManager()
textStorage.addLayoutManager(layoutManager)
textContainer = NSTextContainer()
// I'm not 100% sure if I need this on false or true or can just do defaults. No combination fixes it
// textContainer.heightTracksTextView = false
// textContainer.widthTracksTextView = true
layoutManager.addTextContainer(textContainer)
scrollView = NSTextView.scrollableTextView()
textDocumentView = (scrollView.documentView as! NSTextView)
// Basically commenting this out, stops applying my NSTextContainer, NSLayoutManager and NSTextContainer, but then of course the formatting is not applied. This one line changes it between it works without formatting, or it doesn't work at all. (Unless I have my text view not embedded in a scroll view) then it works but the scrolling of course then does not work.
textDocumentView.textContainer = textContainer
textDocumentView.string = content
textDocumentView.isVerticallyResizable = true
textDocumentView.isHorizontallyResizable = false
super.init(nibName: nil, bundle: nil)
}
override func loadView() {
view = scrollView
}
}
You can actually create the NSScrollView instance with scrollableTextView() and you can get the implicitly created documentView (NSTextView).
Finally, one can assign the existing LayoutManager of the documentView to the own TextStorage class inheriting from NSTextStorage.
In viewDidLoad you could then add the scrollView traditionally to the view using addSubview:. For AutoLayout, as always, translatesAutoresizingMaskIntoConstraints must be set to false.
With widthAnchor and a greaterThanOrEqualToConstant you can define a minimum size, so the window around it cannot be made smaller by the user. The structure also allows potential later simple extensions with additional sticky views (e.g. breadcrumb view etc).
Code
If you implement it this way, then for a small minimal test it might look something like this.
import Cocoa
class ViewController: NSViewController {
private var scrollView: NSScrollView
private var textDocumentView: NSTextView
required init(content: String) {
scrollView = NSTextView.scrollableTextView()
let textDocumentView = scrollView.documentView as! NSTextView
self.textDocumentView = textDocumentView
let textStorage = TextStorage(editorAttributes: MarkdownAttributes())
textStorage.addLayoutManager(textDocumentView.layoutManager!)
textDocumentView.string = content
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
scrollView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(scrollView)
NSLayoutConstraint.activate([
scrollView.topAnchor.constraint(equalTo: view.topAnchor),
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
scrollView.widthAnchor.constraint(greaterThanOrEqualToConstant: 200.0),
scrollView.heightAnchor.constraint(greaterThanOrEqualToConstant: 200.0),
])
}
override func loadView() {
self.view = NSView()
}
}
Test
Here's a quick test showing that both display and scrolling work as expected with the setup:

MPVolumeView thumb image not correctly drawn horizontally

I´ve looked around but can´t find a solution...
I have a MPVolumeView in my app to control the unit´s system volume. I changed the MPVolumeView SliderThumbImage to an image of my own and what I´ve noticed is 2 bugs:
Bug 1 :
The thumb image sometimes gets offset horizontally to the right.
this usually happens when I run from Xcode and the phone´s volume is at maximum. see the image where the bug happened and I bring the volume down to a minimum.
if I bring the volume to minimum, close the app and reopen it, it will be put at the correct location. I think it may have something to do with how the units volume translates into a value for the slider and where the image gets positioned according to said value, but I´m unsure how to solve this. Bug 1
Bug 2: Apple´s own volume indicator layer sometimes shows up and sometimes does not.
I would rather it didn´t at all.
I use a view in the storyboard which I classed MPvolumeView and then in the viewdidload I use the following code to put the image
let faderknob : UIImage = UIImage.init(imageLiteralResourceName:"faderknob50.png")
func setVolumeThumbImage(_ image: UIImage?,for state: UIControl.State){
volumeView.setVolumeThumbImage(faderknob, for: .normal)
volumeView.showsRouteButton = false
}
setVolumeThumbImage(faderknob, for: .normal)
any help on how to fix theses 2 bugs would be great!
Thanks
Was also experiencing your first bug: how higher the volume, how greater the offset of the thumbimage to the right on intial appearance of the volumeview. When the app is relaunched from the background the offset is gone.
When overriding volumeSliderRect in a subclass, the problem is solved:
import MediaPlayer
class VolumeView: MPVolumeView {
override func volumeSliderRect(forBounds bounds: CGRect) -> CGRect {
return bounds
}
}
A side effect is that the thumb image now gets animated from the left to the initial position on first appearance. Actually looks not too bad in my opinion.
As you now have your own subclass, you can set your custom thumbimage during init:
import MediaPlayer
class VolumeView: MPVolumeView {
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
func commonInit() {
setValue(false, forKey: "showsRouteButton") // no deprecated warning
setVolumeThumbImage(UIImage(named: "volume.slider.thumb"), for: .normal)
}
override func volumeSliderRect(forBounds bounds: CGRect) -> CGRect {
return bounds
}
}

Any way to opt out of autoresizing permanently?

I'm writing nib-less views in which I use autolayout for all my layout logic. I find myself having to turn off autoresizing with every view I instantiate. My code is littered with a lot of these:
view.translatesAutoresizingMaskIntoConstraints
Ideally I'd like to just
extension UIView/NSView {
override var translatesAutoresizingMaskIntoConstraints: Bool = false
}
and get it over with once and for all, but extensions can't override stored properties.
Is there some other simple way to switch off autoresizing for good?
Well just a suggestion since its annoying to always set that to false, just setup a function with all the shared setups for the UIView and call it every time,
its saves time and its kinda less annoying than trying and setting the values each time,
extension UIView {
func notTranslated() {
self.translatesAutoresizingMaskIntoConstraints = false
//Add any additional code.
}
}
//Usage
let view = UIView()
view.notTranslated()
You can't override this constraints properties because the UIView maybe declared in the IB
translatesAutoresizingMaskIntoConstraints according to apple.
By default, the property is set to true for any view you programmatically create. If you add views in Interface Builder, the system automatically sets this property to false.
imagine if you could override that from an extension that would lead to some conflicts if there was other UIView's that's have the opposite value True || false, so in my opinion:
Apple did this to prevent any conflicts with the views constrains, therefore if you don't like to write it every time just wrap it up in a function.
Please if anyone have additional information, don't hesitate to contribute.
UPDATE: I found this cool answer that could also work, check out the code below.
class MyNibless: UIView {
//-----------------------------------------------------------------------------------------------------
//Constructors, Initializers, and UIView lifecycle
//-----------------------------------------------------------------------------------------------------
override init(frame: CGRect) {
super.init(frame: frame)
didLoad()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
didLoad()
}
convenience init() {
self.init(frame: CGRect.zero)
}
func didLoad() {
//Place your initialization code here
//I actually create & place constraints in here, instead of in
//updateConstraints
}
override func layoutSubviews() {
super.layoutSubviews()
//Custom manually positioning layout goes here (auto-layout pass has already run first pass)
}
override func updateConstraints() {
super.updateConstraints()
//Disable this if you are adding constraints manually
//or you're going to have a 'bad time'
//self.translatesAutoresizingMaskIntoConstraints = false
translatesAutoresizingMaskIntoConstraints = false
//Add custom constraint code here
}
}
var nibless: UIView = MyNibless()
//Usage
nibless.updateConstraints()
print(nibless.translatesAutoresizingMaskIntoConstraints) //false
So simply just create MyNibless instance as UIView and this also open big door to customizations too

Is IBDesignable broken for AppKit in Xcode 9.2 and Swift 4?

Most questions, and answers related to this, are based on older versions of both Xcode and Swift. Additionally, 90 percent of the questions relate to UIKit and drawing custom controls.
I am adding a standard button, that is centered inside a custom control, decorated with IBDesignable.
import Cocoa
#IBDesignable public class ButtonPresetView: NSView {
public override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
initialControlSetup()
}
public required init?(coder decoder: NSCoder) {
super.init(coder: decoder)
initialControlSetup()
}
private func initialControlSetup() {
let button = NSButton(title: "Hello", target: nil, action: nil)
button.translatesAutoresizingMaskIntoConstraints = false
addSubview(button)
// Configure button
centerXAnchor.constraint(equalTo: button.centerXAnchor).isActive = true
centerYAnchor.constraint(equalTo: button.centerYAnchor).isActive = true
}
}
I add a custom view to the application and set the class property in the Identity Inspector to my custom class (ButtonPresetView).
It should show the button centered on the canvas, but the canvas is blank.
Not sure many people use it this way, but it worked gloriously with Swift 3 in Xcode 8.3.
Does anyone else have this problem?
I was able to get this to work in the latest Xcode by adding the following two lines to the top of the initialControlSetup function:
wantsLayer = true
canDrawSubviewsIntoLayer = true
I think this basically tells the NSView to render in a way that is more similar to how iOS works. If this worked in Xcode 8.3 as you say, it's possible that Apple introduced this regression in Xcode 9 without realizing it.
Dave's answer is correct, I just want to make a note on the consequences of this solution.
When canDrawSubviewsIntoLayer is set to true, all its sub views, that did not enable wantsLayer specifically, will render its contents using the layer of the parent view with canDrawSubviewsIntoLayer set to true.
This means sub view animations is disabled, since they lack a backing layer of their own. To prevent this from happening during runtime, you can put canDrawSubviewsIntoLayer = true into the prepareForInterfaceBuilder() function.
On a curious note, Interface Builder does not render the control, if you explicitly set button.wantsLayer = true, which according to the "canDrawSubviewsIntoLayer" documentation, should give the control its own backing layer and not render itself into the parent layer.
This is purely speculation, but I'm guessing as an optimisation, Interface Builder only renders the top layers/controls of the content view.

NSViewController story board with coded anchors collapses window

I'm having trouble mixing story boards and coded autolayout in Cocoa + Swift. It should be possible right?
I started with a NSTabViewController defined in a story board with default settings as dragged out of the toolbox. I added an NSTextField view via code. And I added anchors. Everything works as expected except the bottom anchor.
After adding the bottom anchor, the window and controller seem to collapse to the size of the NSTextField. I expected the opposite, that the text field get stretched to fill the height of the window.
What am I doing wrong? The literal Frame maybe? Or some option flag that I'm not setting?
class NSTabViewController : WSTabViewController {
var summaryView : NSTextField
required init?(coder: NSCoder) {
summaryView = NSTextField(frame: NSMakeRect(20,20,200,40))
summaryView.font = NSFont(name: "Menlo", size: 9)
super.init(coder: coder)
}
override func viewDidLoad() {
self.view.addSubview(summaryView)
summaryView.translatesAutoresizingMaskIntoConstraints = false
summaryView.topAnchor.constraintEqualToAnchor(self.view.topAnchor, constant: 5).active = true
summaryView.leftAnchor.constraintEqualToAnchor(self.view.leftAnchor, constant: 5).active = true
summaryView.rightAnchor.constraintEqualToAnchor(self.view.rightAnchor, constant: -5).active = true
summaryView.bottomAnchor.constraintEqualToAnchor(self.view.bottomAnchor, constant: -5).active = true
}
To prevent window from collapsing set lower priority for hugging:
summaryView.setContentHuggingPriority(249, forOrientation: .Vertical)
But you actually misuse tab view controller. It just manages views in common use... while you are adding text to the tab header area. There is a very good tutorial of how to use it correctly: https://www.youtube.com/watch?v=TS4H3WvIwpY