Somebody here on SO wanted to alter the behavior of UIAlertAction
buttons in a specific UIAlertController
, but not others. (They wanted multi-line button labels for one alert but normal behavior for all other alerts.) (Here is a link to the other question.)
If you read the docs for UIAlertController
it says that
The UIAlertController class is intended to be used as-is and doesn’t support subclassing. The view hierarchy for this class is private and must not be modified.
As an experiment, I decided to try creating a dummy, empty subclass of UIAlertController
, purely so that I had a class name to give to the UIAppearance
method appearance(whenContainedInInstancesOf:)
The definition of the dummy subclass is just this:
class FooController: UIAlertController {
}
That then lets me use the statement
UILabel.appearance(whenContainedInInstancesOf: [FooController.self]).numberOfLines = 2
and override the appearance of UILabel
s specifically in instances ofFooController
It works, seemingly flawlessly.
The FooController
creates an alert with 2-line button labels like this:
And the Vanilla UIAlertController
creates an alert with 1-line button titles even though it gets passed 2 line titles:
You can download the sample project from Github here.
When you create a vanilla UIAlertController
, its UIAlertAction
buttons have single-line labels as normal. When you create a FooController
, its UIAlertAction
buttons have multi-line labels.
While it seems to work perfectly, I am leery of going against an explicit statement in Apple's docs not to subclass UIAlertController
.
What are the risks of ignoring that admonition and using an empty subclass?
Here is the code from my sample project for reference:
import UIKit
class FooController: UIAlertController {
}
class ViewController: UIViewController {
let buttonLabels = [
"""
Button1
line2
""",
"""
Button2
line2
""",
"""
Button3
line2
"""
]
@IBAction func handleAlertButton(_ sender: Any) {
presentAlert(type: UIAlertController.self)
}
@IBAction func handleFooButton(_ sender: Any) {
presentAlert(type: FooController.self)
}
override func viewDidLoad() {
super.viewDidLoad()
UILabel.appearance(whenContainedInInstancesOf: [FooController.self]).numberOfLines = 2
}
func presentAlert(type: UIAlertController.Type) {
let sheet = type.init(title: type.description(), message: nil, preferredStyle: .actionSheet)
for buttonTitle in buttonLabels {
let item = UIAlertAction(title: buttonTitle, style: .default) { (action) in
print("Button \(buttonTitle) tapped")
}
sheet.addAction(item)
}
present(sheet, animated: true, completion: nil)
}
}
What are the risks of ignoring that admonition and using an empty subclass?
UIAlertController
might not function correctly in a future update of iOS.UILabel.appearance
) for the subclass might not function correctly in a future update of iOS.Those are the possible risks.
While Apple always has the final say, it's highly unlikely that Apple would reject an app simply because you subclassed UIAlertController
in such a manner. I've personally subclassed several UIKit classes that Apple says should not be subclassed. I've done this in an app that has been in the app store for many years and has had many updates. You are not using any private APIs. And Apple does not say that you can't subclass UIAlertController
. It says that the class isn't intended to be subclassed.
This leads to risk #2. Apple states that UIAlertController
isn't intended to be subclassed and it doesn't support subclassing. This means that it does not provide any API that is meant to be overridden or modified. But it does not mean that you can't subclass it to add helper methods, for example. Or simply to give the class a new name such that you can do things like your UILabel.appearance
. Your subclass is benign and makes no attempt to modify the functionality or dig into the private subview structure. It's a "safe" subclass that won't break any existing functionality.
Lastly, risk #3. While minor, this is probably the "biggest" risk of the 3. Apple could make any number of changes to UIAlertController
in a future update to iOS that might hinder or break the desired result of using UILabel.appearance
on your subclass. Testing the code on every release of iOS would be prudent. Likely, the worst case will be that the "hack" will stop working.