How to disable 3D Touch Link Preview - swift

Since iOS10 the "3D Touch link preview" has been enabled by default, but how can I disable it in my app? Documentation says it's possible by setting allowsLinkPreview to false, but doing it separately for each use case is too much code. There must be an easier way, right?
I thought this would do it, but I get error "Property does not override any property from its superclass". I hope there's just a small mistake, help would be appreciated:
extension WKWebView {
override open var allowsLinkPreview: Bool {
set {
// no-op
}
get {
return false
}
}
}

You could extend WKWebView with a convenience initializer and set the property inside of it. Then use this initializer to instantiate web views.
extension WKWebView {
convenience init(allowsLinkPreview: Bool) {
self.init()
self.allowsLinkPreview = allowsLinkPreview
}
}
Another option is to make a subclass of WKWebView and add this convenience initializer there.

Related

How to override readonly property in Swift subclass, make it read-write, and assign to superclass?

How can I override readonly property of a Swift superclass, from a subclass, to make the property read-write?
I want to do that, because that is what the iOS UIResponder documentation says is required. However, I'm getting an error when I try to implement what I think the Swift 5 documentation says can be done:Swift 5 documentation
Inheritance
Overriding Property Getters and Setters:
You can present an inherited read-only property as a read-write property by providing both a getter and a setter in your subclass property override.
What Went Wrong:
Based on aforementioned Swift docs statement, for which I found no accompanying example, I created the following subclass, at which XCode generates this error message:
   "Cannot assign to property: 'inputAccessoryViewController' is a get-only property"
My Subclass:
class InputAccessoryEnabledTextView : UITextView {
override var inputAccessoryViewController: UIInputViewController? {
get { super.inputAccessoryViewController }
set { super.inputAccessoryViewController = newValue }
}
}
UIResponderDeclarationvar inputAccessoryViewController: UIInputViewController? { get }Discussion
This property is typically used to attach an accessory view controller to the system-supplied keyboard that is presented for UITextField and UITextView objects.
The value of this read-only property is nil. If you want to attach custom controls to a system-supplied input view controller (such as the system keyboard) or to a custom input view (one you provide in the inputViewController property), redeclare this property as read-write in a UIResponder subclass. You can then use this property to manage a custom accessory view. When the receiver becomes the first responder, the responder infrastructure attaches the accessory view to the appropriate input view before displaying it.
Note: I do see a question from 6 years ago on Stack Overflow that got no accepted answers and doesn't seem to properly answer the question, so please don't flag this as a dup without a good reason. Perhaps these questions can be merged later
super.inputAccessoryViewController is not settable.
Your overridden implementation in the subclass, self.inputAccessoryViewController is.
By adding a setter to the property in a subclass, you don't automatically also add the same thing in the superclass. What's in the subclass stays in the subclass.
So it's not that you can't override a property by adding a setter, you just can't set this here:
override var inputAccessoryViewController: UIInputViewController? {
get { super.inputAccessoryViewController }
set { super.inputAccessoryViewController = newValue }
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
}
You can do other things, like:
override var inputAccessoryViewController: UIInputViewController? {
get { super.inputAccessoryViewController }
set { print("I just go set to \(newValue)") }
}
But that's not very useful. What you want is probably:
private var myInputAccessoryController: UIInputViewController?
override var inputAccessoryViewController: UIInputViewController? {
get { myInputAccessoryController }
set { myInputAccessoryController = newValue }
}

How to resolve ambiguity between property in extension and property in class?

I made an extension to UIView to add the basic shadow properties so that I could set them using #IBInspectable.
extension UIView {
var shadowRadius: CGFloat {
get {
return layer.shadowRadius
}
set {
layer.shadowRadius = newValue
}
}
}
However, I now want to use a library that has redefined the property:
class ClassA: UIView {
#objc public dynamic var shadowRadius = DPDConstant.UI.Shadow.Radius {
willSet { tableViewContainer.layer.shadowRadius = newValue }
didSet { reloadAllComponents() }
}
}
When I try to call the property I get an "ambiguous use of shadowRadius" message.
I am considering just removing the extension and manually calling layer.shadowRadius since I am no longer using IBInspectable on shadows (seemed to be running slow loading all the IBInspectable properties, so I opted to just set stuff in code).
However, I feel like there ought to be another way to handle this situation, so I thought I would ask here.
Your extension provides this var for all instances of UIView and subclasses. The subclass in the library defines a subclass-specific property of the same name and type. I don’t have the reference but the Swift language documentation says you can’t override an extension’s members with a subclass.

Can I observe an optional value in swift? If not, how might I go about attempting to observe a change?

I'm trying to observe a change to the selection of an NSPopUpButton in Swift 4. In my view controller's viewDidLoad() I've set up the observation token to observe the selectedItem property of the NSPopUpButton
override func viewDidLoad() {
super.viewDidLoad()
observation = observe(\.myPopUpButton.selectedItem) {
objectToObserve, change in
if change.kind == NSKeyValueObservedChange.Kind.setting {
// code to execute goes here
}
}
I set a breakpoint on the line where observation is set to determine that the token is being configured with the correct key path. I also set a break inside the closure to see when it is executed. When I change the selection of the NSPopUpButton, the closure does not execute.
selectedItem is of type, NSMenuItem?, so my suspicion is that I can't set an observation on an optional property. But I can't find anything in Apple's documentation that states whether or not that is the case and I'm not sure how I would go about verifying it for myself.
So I have sort of a primary question along w/ some followups:
Can I observe an optional property in Swift 4.1?
If so, how can I troubleshoot this, what am I doing wrong?
If not, how can I go about trying to monitor the state of the NSPopUpButton?
Troubleshoots that I've already tried...
added #objc dynamic to the my myPopUpButton declaration
Many properties of many AppKit objects are not KVO-compliant. Unless the documentation specifically says the property is compliant, you should assume it's not compliant. NSPopUpButton's selectedItem property is non-compliant.
The easiest way to be notified that the pop-up button's selected item changed is to set the button's target and action:
override func viewDidLoad() {
super.viewDidLoad()
myPopUpButton.target = self
myPopUpButton.action = #selector(popUpButtonDidFire(_:))
}
#IBAction private func popUpButtonDidFire(_ sender: Any) {
// code to execute goes here
}
Note that if you're creating the pop-up button in a storyboard or xib, you can wire it to the popUpButtonDidFire method by control-dragging from the pop-up button to the view controller.
As mentioned in the comments in macOS Cocoa Bindings and Swift's property observers are a very powerful way to observe values, even in prior Swift versions. An outlet is not needed.
Create a property and use the didSet observer
#objc dynamic var selectedObject : MyObject? {
didSet {
}
}
In Interface Builder in Bindings Inspector bind Selected Object to the target controller Model Key Path > selectedObject.
MyObject is the type of the represented object of the menu item. If nothing is selected selectedObject is nil. You can bind also Selected Index, Selected Tag or Selected Value (but not simultaneously).

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.

performSegueWithIdentifier from a NSView subclass?

I have a document window that contains a number of NSView subclasses, switched between using a tab control. Each of the subclasses, and the window's ViewController, support different user actions accessed through menu items tied to the First Responder.
I'd like to perform a segue from one of those views in response to a menu item. However, NSView does not support performSegueWithIdentifier, it appears to be something that is part of NSViewController alone.
Can someone suggest a way around this? I have seen suggestions to pass the VC into the views, but I am not clear how to do that. Or perhaps there is a better way?
view.containingController.performSegue()
note: you have to add containingController to your views
I WOULD add the viewController to the responder chain and then make containingController a computed property in an extension!
e.g. add vc as responder:
override func awakeFromNib() {
super.awakeFromNib()
self.nextResponder = self.view
for subview in self.view.subviews {
subview.nextResponder = self
}
}
e.g. containingController in extension
extension NSView {
var containingController: NSViewController? {
get {
while(self.nextResponder != nil) {
if(self.nextResponder is NSViewController) {
return self.nextResponder
}
}
return nil
}
}
}
You could do that (see Daij-Djan's answer), however it is not what I would recommend, since a hypothetical programmer who will be using your code, but is not familiar with it (let's say, you in a year :) ) might be caught by surprise by such behaviour.
I would recommend you to add a delegate (conforming to your custom protocol, let's call it MyViewDelegate) to your NSView with a method like viewRequiresToPerformTransition(view: YourViewSubclass). Then you implement this method (more generally, you conform to MyViewDelegate protocol) in your view controller and inside its implementation perform any segue you want.