I have a custom UItextView subclass where I override canBecomeFirstResponder():
class MyTextView: UITextView {
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func drawRect(rect: CGRect) {
// Drawing code
}
*/
override func canBecomeFirstResponder() -> Bool {
return false
}
}
I'm doing this to allow the data detected links (phone numbers are URLs) in a UITextView to function without any of the other text in the view to be selectable, but that is unrelated to the question.
canBecomeFirstResponder() is the only property/method I want to override, so subclassing seems like overkill. I use this custom class with a view created with Interface Builder. Is there a way I can lazily override an IBOutlet's of a UIKit object's properties? Something like this:
#IBOutlet weak var contactTextView: UITextView! {
override func canBecomeFirstResponder: Bool = {
return false
}
}
I do not want to use an extension on UITextView because I only want to override canBecomeFirstResponder() for a specific UITextView, not every one used in my project.
If you want to override, you have to subclass. These two concepts are connected. You cannot override without subclassing.
Your "like this" idea wouldn't work in any language. Basically, you are describing an anonymous subclass which is available in Java, for instance (not in Swift). However, that would have to be used during class instantiation, not when declaring a variable.
Of course, you could swizzle canBecomeFirstResponder with a method returning false in didSet but that's much more complicated than subclassing.
The answer is that with the current versions of swift, there is no way to do that.
Based on what you are describing, it might be possible that you could make your view controller implement UITextViewDelegate and implement textViewShouldBeginEditing to return false.
This is just a guess based on the fact that you're seemingly trying to override canBecomeFirstResponder to disallow typing.
That all being said, as was pointed out if you return false from canBecomeFirstResponder I think the expected behavior is that the UI element will no longer allow itself to capture user input. From your responses in the comments, it seems that its capturing user input for you anyway, but that might just be a the particular version of iOS you're running. It could also be that my understanding of the first responder chain is incorrect.
Related
I have two main screens in my app, currently both just subclasses of UIViewController. These two view controllers are very similar - they both implement my custom subclass of UIView called HeaderView that is responsible for displaying information and taking user input. As it stands, this code is repetitive because the HeaderView setup is the same for both view controllers - the only difference is what happens when the user confirms the text entry in HeaderView.
To cut down on repetitive code, I am creating a class called InputViewController (a subclass of UIViewController) that houses the aspects of the two view controllers that are identical. Eventually, I want the two view controllers to subclass InputViewController instead of UIViewController.
class InputViewController: UIViewController, InputProtocol {
private let headerView = HeaderView()
override func viewDidLoad() {
super.viewDidLoad()
// layout, etc.
setupCallbacks()
}
internal func setupCallbacks() {
headerView.onUpdate = { (text: String) in
// called when user confirms text entry in headerView
self.onHeaderUpdate()
}
}
internal func onHeaderUpdate() {} // Blank function
}
setupCallbacks() and onHeaderUpdate() are methods defined in the protocol that the InputViewController conforms to. The HeaderView implements a callback closure that is handled in setupCallbacks() by headerView.onUpdate...
The protocol that InputViewController conforms to:
protocol InputProtocol {
func setupCallbacks()
func onHeaderUpdate()
}
To illustrate this, I drew up a diagram;
Since I want the subclasses of InputViewController to override the onHeaderUpdate() method, is it conventional to leave the definition of onHeaderUpdate() in InputViewController blank or is there another solution to this?
is it conventional to leave the definition of onHeaderUpdate() in InputViewController blank
Yes, that is called an abstract method. It is common to give it code that crashes deliberately, as a way of saying, “I exist only to be overridden in a subclass.”
(I should go further and say that what you are creating, a base view controller that carries out initial configurations that all subclasses must implement, is also normal.)
Is it possible to create some sort of generic willSet closure to style IBOutlets in a view controller?
Currently I use this code to set the tint color of UIImageView once its set. ".primary" is a a static variable on UIColor which I created via a UIColor extension:
#IBOutlet weak var someImageView:UIImageView! {
willSet {
newValue.tintColor = .primary
}
}
Now I have a whole bunch of outlets which I want to style in the same way and for me that seems like a lot of duplicated code, especially if I start to apply more styling than just setting the tint so I want something more generic. I came up with:
let tintClosure: (_ newValue: UIView?) -> () = { $0?.tintColor = .primary }
#IBOutlet weak var someImageView:UIImageView! { willSet{ tintClosure(newValue) } }
Now I have to write the code for the actual tinting only once and can just call the closure in willSet. However, I wonder if there is an even more efficient way so I don't even have to call the closure with (newValue) by myself but just giving it the tintClosure.
Like if a function expects a completion handler and you have a function which fits its declaration and you just pass the name of the function instead of a closure in which you call the function.
Something really fancy like:
#IBOutlet weak var someImageView:UIImageView! { superDuperClosure(willSet) }
or
#IBOutlet weak var someImageView:UIImageView! { willSet{ crazyImplementationOfTintClosre } }
or
#IBOutlet weak var someImageView:UIImageView! { willSet.howIsThisEvenPossible }
or
#IBOutlet weak var someImageView:UIImageView! { nowWeAreTalking }
Well, maybe I'm just thinking over the top and I already found the most simplistic way of doing it. Maybe I don't have a deep enough understanding of how willSet works. So dear Swift gurus, teach me how to become a swifty developer.
Update
Based on the second suggestion from Paulo Mattos I had another idea. I created a outlet collection containing all the image views. Then I just iterate over all of them tinting each one with this nice little piece of code:
#IBOutlet var imageViewsToTint: [UIImageView]! { willSet { newValue.forEach { $0.tintColor = .primary } } }
The wonderful thing with this approach is, that you basically can create outlet collection for various purposes like one for tinting, one for text size etc. and then you can just connect the views you want to style to the appropriate collections.
Your solution already feels pretty minimalistc to me :) I can't come up with a shorter hack around that, sorry. This, somewhat exotic, Swift Evolution proposal could provide some help, but it was postponed for now at least.
Let's quickly explore some alternatives beyond willSet (probably gonna be downvoted for this, anyway...):
UIView subclass. I have a UITextField that I use across my app a lot! As such, I implemented a simple subclass and did all styling in there (instead of on each view controller which it appears). Of course, this might not be viable across all your views.
viewDidLoad. You could group all your views in an array and then do the styling inside the viewDidLoad method. Might be more error prone than your current solution — you might miss a view here and there — but gets the job done without too much hacking ;)
In my app I have some interface elements such as :CustomLabel, CustomView, CustomTextField (all are custom classes inherit from their base class: UIView, UILabel, UITextField.
At this moment I'm calling from each element DrawRect callback to a function that draws underline on itself and basically I have chunks of duplicated code on each subclass.
How can I make this three subclasses have the same DrawRect content without creating an extension of UIView because I don't want each View or subclass of UIView to have this behaviour.
For this you could use a protocol and a protocol extension to add your common methods to your subclasses. Then you'd only have to make your classes conform to that protocol and override drawRect to call the methods from your protocol. Adding the new behaviour by just conforming to the protocol and not doing any other changes is not possible, unfortunately. You can't override methods in protocol extensions, and you can't add methods that use the Objective-C runtime there either which would allow method swizzling.
In code this would look something like this:
protocol CustomDrawing {}
extension CustomDrawing where Self: UIView {
func myDrawingCode() {
// Whatever
}
}
You use it like this then:
class CustomLabel: UILabel, CustomDrawing {
override func draw(_ rect: CGRect) {
super.draw(rect)
myDrawingCode()
}
}
If you need to access some common property or method in myDrawingCode() you will have to declare them inside the CustomDrawing protocol. UIView methods are available since the protocol extension is constrained to UIView and subtypes.
I've had this working in other variations but something seems to elude me in the change from objective-c to swift as well as moving some of the setup into it's own class.
So i have:
class ViewController: UIViewController, interfaceDelegate, scrollChangeDelegate{
let scrollControl = scrollMethods()
let userinterface = interface()
override func viewDidLoad(){
super.viewDidLoad()
loadMenu("Start")
}
func loadMenu(menuName: String) {
userinterface.delegate = self
userinterface.scrollDelegate = self
userinterface.removeFromSuperview() //no impact
scrollControl.removeFromSuperview() //no impact
userinterface.configureView(menuName)
view.addSubview(scrollControl)
scrollControl.addSubview(userinterface)
}
}
This sets everything up correctly but the problem occurs when I change loadMenu() at runtime. So if the user calls loadMenu("AnotherMenu") it won't change the UIView. It will call the right functions but it won't update the view. Although if I call loadMenu("AnotherMenu") at the start, the correct menu will display. Or if I call loadMenu("Start") and then loadMenu("AnotherMenu") then the menu displayed will be "AnotherMenu". As in:
override func viewDidLoad(){
super.viewDidLoad()
loadMenu("Start")
loadMenu("AnotherMenu")
}
When I list all the subviews each time loadMenu() is called, they look correct. Even during runtime. But the display is not updated. So something isn't getting the word. I've tried disabling Auto Layout after searching for similar issues but didn't see a difference.
Try adding setNeedsDisplay() to loadMenu
Eg
func loadMenu(menuName: String) {
userinterface.delegate = self
userinterface.scrollDelegate = self
userinterface.removeFromSuperview() //no impact
scrollControl.removeFromSuperview() //no impact
userinterface.configureView(menuName)
view.addSubview(scrollControl)
scrollControl.addSubview(userinterface)
view.setNeedsDisplay()
}
setNeedsDisplay() forces the view to reload the user interface.
I didn't want to post the whole UIView class as it is long and I thought unrelated. But Dan was right that he would need to know what was going on in those to figure out the answer. So I created a dummy UIView class to stand in and intended to update the question with that. I then just put a button on the ViewController's UIView. That button was able to act on the view created by the dummy. So the problem was in the other class. Yet it was calling the methods of the ViewController and seemingly worked otherwise. So then the issue must be that its acting on an instanced version? The way the uiview class worked, it uses performSelector(). But in making these methods into their own class, I had just lazily wrote
(ViewController() as NSObjectProtocol).performSelector(selector)
when it should have been
(delegate as! NSObjectProtocol).performSelector(selector)
so that was annoying and I wasted the better part of a day on that. But thanks again for the help.
I would like to programmatically disable or enable the auto rotate function using a button using Swift. I was thinking that it might be able to somehow be done using the supportedInterfaceOrientations() function, but I am very confused after looking through the literature about how this is done. Is there an easy solution for this?
You could create an action for the button that sets a boolean flag somewhere in your code and return the value of that flag in the shouldAutorotate method of the view controller. If you need that for all view controllers you could create a common base view controller (inheritance).
Example of button action:
#IBAction func toggleRotation(sender: Button) {
// A made up AppConfig class with class method for setting and retrieving
// rotation flag.
AppConfig.allowRotation(!AppConfig.allowRotation)
}
Example of shouldAutorotate:
override func shouldAutorotate() -> Bool {
return AppConfig.allowRotation()
}
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIViewController_Class/#//apple_ref/occ/instm/UIViewController/shouldAutorotate