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

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.

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 disable 3D Touch Link Preview

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.

Subclassing Views

I want to subclass UIView to support gradients (border / fill). I have subclass with #IBInspectable vars, so I'm able to setup this behavior in IB.
I also need to subclass also UIButton with the same methods. Is there any way I can do it without copying all the methods and instance variables to that subclass of UIButton?
Multiple class inheritance is not allowed in Swift (only multiple protocol inheritance), therefore what you are trying to achieve is not straight-forwardly possible.
One of the possible workarounds, however, is to use extension for UIView. Provided that both UIView (itself) and UIButton are variants of UIView, the following code would apply to them all.
Example with corner radius:
extension UIView {
#IBInspectable var cornerRadius: CGFloat {
get {
return layer.cornerRadius
}
set {
layer.cornerRadius = newValue
layer.masksToBounds = newValue > 0
}
}
}
Now, even non-subclassed UIButton will acquire this property, which will be reflected in Interface Builder.
You can try employing the power of extensions to implement unified IBInspectables. One obstacle you will inevitably stumble across, however, is that you won't be able to have any storage in the extension. But for several cases this can serve as a solution.
P.S. Few other examples of use (added to UIView extension):
#IBInspectable var borderColor: UIColor? {
get { return layer.borderColor.map(UIColor.init) }
set { layer.borderColor = newValue?.cgColor }
}
#IBInspectable var borderWidth: CGFloat {
get { return layer.borderWidth }
set { layer.borderWidth = newValue }
}

Swift iOS -How to Create A Property with Getter and Setter on Extension of UIViewController Class

I know in Swift your not allowed to use properties inside extensions.
I have a property and several functions that I use throughout my app in several different classes. I want to extend the UIViewController class to include the property and functions. I know that you can't add properties to extensions in Swift. I tried a computed property but it wouldn't accept that either so I resorted to a getter and setter. It's accepting it but it's not working properly because the property isn't getting set.
What I'm looking to do:
I want to be able to call steps 4 and 5 in different classes instead of using this same code over and over. Steps 4 and 5 access the same property which is initialized from another function inside the class.
Steps:
I add the property with a getter/setter and 3 functions as extensions to the UIViewController class.
the property type is a UIView that gets initialized from a function inside the class.
the function contains a label, a localView (type uiview), and an activity indicator. This function adds the label and actInd as subViews to the localView and then returns the localView.
the property from step one gets initialized with return value from the function in step 2.
another function accesses the property and adds it as a subView to the UIViewController class
another function accesses the same property and removes it from the UIViewController class.
I tried to use a getter and setter but it's not working.
Code:
extension UIViewController{
//step 1
var stepOneSetView: UIView? {
get {
return UIView()
}set {
return self.stepOneSetView = stepTwoCreateViewWithLabelAndActInd()
}
}
//step 2
func stepTwoCreateViewWithLabelAndActInd() -> UIView{
var localView = UIView()
var actInd = UIActivityIndicatorView()
var label = UILabel()
//do whatever then add actInd and label to localView
return localView
}
//step 3 is inside the setter of stepOneSetView
//step 4
func stepFourAddViewFromStepOneToViewController(){
view.addSubview(stepOneCreateView!)
}
//step 5
func stepFiveRemoveViewFromStepOneFromViewController(){
stepOneCreateView!.removeFromSuperview()
}
}
Now when I subclass the UIViewController class I can use functions step 4 and 5:
class BlaBlaBla: UIViewController{
override func viewDidLoad() {
super.viewDidLoad()
self.stepFourAddViewFromStepOneToViewController()
//some timer happens and then
self.stepFiveRemoveViewFromStepOneFromViewController()
}
}
Where am I going wrong at?

Is there an equivalent of CSS classes on controls in Swift and iOS?

I have a lot of UIButtons on a view that have to be styled through code. I need to give them all rounded borders, which cannot be done in XCode's interface builder.
So I'm wondering, is there a way in Swift to style a whole bunch of elements all at once, like using a CSS class to style stuff on the web?
Each button has an IBOutlet in my controller and it would be nice to style them all at the same time.
"which cannot be done in XCode's interface builder".
Sure it can, but you need to consider the various factors which limit you to think in terms of CSS.
Design an extension to a UIButton. Then make it IBDesignable. If you want to actually see it in IB, make it IBInspectable. Maybe your code will look something like this:
#IBDesignable
public class Button: UIButton {
#IBInspectable public var borderColor:UIColor? {
didSet {
layer.borderColor = borderColor?.cgColor
}
}
#IBInspectable public var borderWidth:CGFloat = 0 {
didSet {
layer.borderWidth = borderWidth
}
}
#IBInspectable public var cornerRadius:CGFloat {
get {
return layer.cornerRadius
}
set {
layer.cornerRadius = newValue
layer.masksToBounds = newValue > 0
}
}
}
In particular, pay attention to the cornerRadius inspectable property.
Finally here's a link that however old, I still find worthy of explaining things better than I can.