how to override an open var in a fileprivate extension - ios

In Swift, how to override an open var in a fileprivate extension, or is there any way to achieve the goal?
There is a table view in a view controller, when I touch a button, the property of isUserInteractionEnabled will be set opposite, then the alpha of the table view will change according to the isUserInteractionEnabled.
And I want to use property observation, overriding isUserInteractionEnabled in a UITableView extension
fileprivate extension UITableView {
open override var isUserInteractionEnabled: Bool {
didSet {
alpha = isUserInteractionEnabled ? 1.0 : 0.6
}
}
}
The code embeds in a UIViewController, I want this extension just takes effect inside this view controller. But it turns out it does not work. This extension takes effect everywhere I use a UITableView.
How should I override the isUserInteractionEnabled in extension and make it just take effect in specific file. I am avoiding writing code like below, it seems painful to maintain.
tableView.alpha = 0.6
tableView.isUserInteractionEnabled = false

Related

Subscribe to a variable of a custom UIView

I have a custom View, and in this custom View I declared var isSelected: false that is gonna be toggle when taping on the view.
After I add two of those custom Views in my ViewController.
What I need is: When I select one of view, the other one is immediately deselected, so only one can be selected at the same time.
I don't have much knowledge about it, but I assume that with RxCocoa (or ideally RxSwift) it might be possible to set this isSelected variable of each view as an observable, and then in the subscription, set the other one to false once it turns true.
Help will be much appreciated, thank you in advance.
I know what you are asking for seems like a reasonable idea, but it's not. Your isSelected boolean won't change state unless you specifically write code to make it change state. That begs the question, why is other code monitoring your view's isSelected boolean rather than the event that causes the boolean to change state? Why the middle man? Especially a middle-man that is part of your View system, not your Model.
The appropriate solution is to have an Observable in your model that is bound to your two views...
Better would be something like:
class CustomView: UIView {
var isSelected: Bool = false
}
class Example {
let viewA = CustomView()
let viewB = CustomView()
let disposeBag = DisposeBag()
func example(model: Observable<Bool>) {
disposeBag.insert(
model.bind(to: viewA.rx.isSelected),
model.map { !$0 }.bind(to: viewB.rx.isSelected)
)
}
}
extension Reactive where Base: CustomView {
var isSelected: Binder<Bool> {
Binder(base, binding: { view, isSelected in
view.isSelected = isSelected
})
}
}

UIColor return wrong values for dark mode colors

I have a custom UITextField subclass which changes its border color when typing something in it. I'm listening for changes by calling
self.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged)
and then, in textFieldDidChange(_:) I'm doing:
self.layer.borderColor = UIColor(named: "testColor")?.cgColor
Where testColor is a color defined in Assets.xcassets with variants for light and dark mode. The issue is that UIColor(named: "testColor")?.cgColor seems to always return the color for the light mode.
Is this a bug in iOS 13 beta or I'm doing something wrong? There's a GitHub repo with the code which exhibits this behaviour. Run the project, switch to dark mode from XCode then start typing something in the text field.
Short answer
In this situation, you need to specify which trait collection to use to resolve the dynamic color.
self.traitCollection.performAsCurrent {
self.layer.borderColor = UIColor(named: "testColor")?.cgColor
}
or
self.layer.borderColor = UIColor(named: "testColor")?.resolvedColor(with: self.traitCollection).cgColor
Longer answer
When you call the cgColor method on a dynamic UIColor, it needs to resolve the dynamic color's value. That is done by referring to the current trait collection, UITraitCollection.current.
The current trait collection is set by UIKit when it calls your overrides of certain methods, notably:
UIView
draw()
layoutSubviews()
traitCollectionDidChange()
tintColorDidChange()
UIViewController
viewWillLayoutSubviews()
viewDidLayoutSubviews()
traitCollectionDidChange()
UIPresentationController
containerViewWillLayoutSubviews()
containerViewDidLayoutSubviews()
traitCollectionDidChange()
However, outside of overrides of those methods, the current trait collection is not necessarily set to any particular value. So, if your code is not in an override of one of those methods, and you want to resolve a dynamic color, it's your responsibility to tell us what trait collection to use.
(That's because it's possible to override the userInterfaceStyle trait of any view or view controller, so even though the device may be set to light mode, you might have a view that's in dark mode.)
You can do that by directly resolving the dynamic color, using the UIColor method resolvedColor(with:). Or use the UITraitCollection method performAsCurrent, and put your code that resolves the color inside the closure. The short answer above shows both ways.
You could also move your code into one of those methods. In this case, I think you could put it in layoutSubviews(). If you do that, it will automatically get called when the light/dark style changes, so you wouldn't need to do anything else.
Reference
WWDC 2019, Implementing Dark Mode in iOS
Starting at 19:00 I talked about how dynamic colors get resolved, and at 23:30 I presented an example of how to set a CALayer's border color to a dynamic color, just like you're doing.
You can add invisible subview on you view and it will track traitCollectionDidChange events.
example:
import UIKit
extension UIButton {
func secondaryStyle() {
backgroundColor = .clear
layer.cornerRadius = 8
layer.borderWidth = 1
layer.borderColor = UIColor(named: "borderColor")?.cgColor
moon.updateStyle = { [weak self] in
self?.secondaryStyle()
}
}
}
extension UIView {
var moon: Moon {
(viewWithTag(Moon.tag) as? Moon) ?? .make(on: self)
}
final class Moon: UIView {
static var tag: Int = .max - 1
var updateStyle: (() -> Void)?
static func make(on view: UIView) -> Moon {
let moon = Moon()
moon.tag = Moon.tag
view.addSubview(moon)
return moon
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
guard previousTraitCollection?.userInterfaceStyle != traitCollection.userInterfaceStyle else { return }
triggerUpdateStyleEvent()
}
func triggerUpdateStyleEvent() {
updateStyle?()
}
}
}

Update subviews of UIView from ViewController

I have a UIViewController that implements a custom UIView, so;
override func loadView() {
view = CustomView()
}
The custom view has a few lables and buttons and all the normal stuff, problem is in my viewController I have a request, and when that request is done, I'd like to update some of those lables/buttons.
Right now, in my CustomView, I have functions, such as;
func updateView() {
labelOne.isHidden = true
LabelTwo.isHidden = false
}
So I call the appropriate function from my viewController when the request is done.
This works, but it feels wrong, is there a neater way to update the subviews of my custom UIView, from my viewController? Should I maybe be using protocols or delegates?
One thing I've found quite neat in the past is passing the model directly to the custom view, then using didSet to trigger updates.
class CustomView: UIView {
let labelOne = UILabel()
let labelTwo = UILabel()
var object:CustomObject! {
didSet {
self.labelOne.text = object.name
self.labelTwo.text = object.description
}
}
...
}
This means in your UIViewController you can do the request and then pass the model straight to the custom view.
RequestHelper.getObject() { object in
self.customView.object = object
}
Obviously here I'm guessing at your request and object names but hopefully you get the idea.

Access TextField from another class or method without storyboard

Okay, this might be one of the most basic questions ever, but all answers I find use storyboard to declare an outlet for a label, textfield or whatever element that needs to be changed. I, however, don't use storyboards and write everything in code. Now I have a function setupViews, where I define a textfield:
let usernameInput = UITextField()
Now, I can perfectly set the text or placeholder or whatever inside this setupViews() class, but how can I access it outside? For example, if I have a function logIn(), I want to call usernameInput.text and use it in this function.
Someone who can point me in the right direction? Do I need to declare this textfield globally, in another file, or something else?
When I create my views in code I always associate a property with the view that has all those various display values.
I have not tested this code to see but hopefully the following will give you an idea.
import UIKit
struct {
var name: String
}
class CustomViewController : UIViewController {
// some struct which contains data for view
var customViewData : ViewDataInfo? {
didSet {
labelOnScreen.text = customViewData.name
}
}
var labelOnScreen: UILabel = {
let label = UILabel()
label.text = "Placeholder information..."
// stuff auto layout
label.translateAutoresizingMaskIntoConstraints = false
return label
}()
override func viewDidLoad() {
super.viewDidLoad()
setupView()
}
private func setupView() {
view.addSubview(label)
// set your constraints here
}
}

Passing data between views in ONE ViewController in Swift

All of the searches I've done focus on passing data between view controllers. That's not really what I'm trying to do. I have a ViewController that has multiple Views in it. The ViewController has a slider which works fine:
var throttleSetting = Float()
#IBAction func changeThrottleSetting(sender: UISlider)
{
throttleSetting = sender.value
}
Then, in one of the Views contained in that same ViewController, I have a basic line that (for now) sets an initial value which is used later in the DrawRect portion of the code:
var RPMPointerAngle: CGFloat {
var angle: CGFloat = 2.0
return angle
}
What I want to do is have the slider's value from the ViewController be passed to the View contained in the ViewController to allow the drawRect to be dynamic.
Thanks for your help!
EDIT: Sorry, when I created this answer I was having ViewControllers in mind. A much easier way would be to create a method in SomeView and talk directly to it.
Example:
class MainViewController: UIViewController {
var view1: SomeView!
var view2: SomeView!
override func viewDidLoad() {
super.viewDidLoad()
// Create the views here
view1 = SomeView()
view2 = SomeView()
view.addSubview(view1)
view.addSubview(view2)
}
#IBAction func someAction(sender: UIButton) {
view1.changeString("blabla")
}
}
class SomeView: UIView {
var someString: String?
func changeString(someText: String) {
someString = someText
}
}
Delegate:
First you create a protocol:
protocol NameOfDelegate: class { // ": class" isn't mandatory, but it is when you want to set the delegate property to weak
func someFunction() // this function has to be implemented in your MainViewController so it can access the properties and other methods in there
}
In your Views you have to add:
class SomeView: UIView, NameOfDelegate {
// your code
func someFunction() {
// change your slider settings
}
}
And the last step, you'll have to add a property of the delegate, so you can "talk" to it. Personally I imagine this property to be a gate of some sort, between the two classes so they can talk to each other.
class MainViewController: UIViewController {
weak var delegate: NameOfDelegate?
#IBAction func button(sender: UIButton) {
if delegate != nil {
let someString = delegate.someFunction()
}
}
}
I used a button here just to show how you could use the delegate. Just replace it with your slider to change the properties of your Views
EDIT: One thing I forgot to mention is, you'll somehow need to assign SomeView as the delegate. But like I said, I don't know how you're creating the views etc so I can't help you with that.
In the MVC model views can't communicate directly with each other.
There is always a view controller who manages the views. The views are just like the controllers minions.
All communication goes via a view controller.
If you want to react to some view changing, you can setup an IBAction. In the method you can then change your other view to which you might have an IBOutlet.
So in your example you might have an IBAction for the slider changing it's value (as in your original question) from which you could set some public properties on the view you would like to change. If necessary you could also call setNeedsDisplay() on the target view to make it redraw itself.

Resources