Conditionally cast of generic view controller fails - ios

Say I have the following:
class ContentSelectableViewController<T: NSManagedObject> : UIViewController { //... }
class PersonSelectionViewController: ContentSelectableViewController<Person> { // ... }
class PlaceSelectionViewController: ContentSelectableViewController<Place> { // ... }
Then in an instance of one of these subclasses, I have some code:
if let navCtrl = self.navigationController {
for viewController in navCtrl.viewControllers.reversed() {
if viewController is ContentSelectableViewController {
log.info("Worked for \(viewController.description)")
}
if let vc = viewController as? ContentSelectableViewController {
// This should be equivalent to the above.
}
}
}
My question is, when I have a stack full of subclasses of this generic baseclass, it doesn't always return true (go into the if statement) when checking if they are of type ContentSelectableViewController and I don't understand why. They inherit from the same baseclass.
EDIT:
I'm guessing it's because of the generic nature of the class. The if statements evaluate to true for the subclass that calls it.

So, it does in fact have something to do with trying to type check a generic class. It would work for the one and not the other because the one making the call implicitly adds its type.
i.e. (Pseudo-Swift)
if viewController is ContentSelectableViewController<Person> { //... }
What I did instead was to define a protocol that ultimately makes these ContentSelectableViewController<T> selectable:
enum ContentSelectionRole: Int {
case none = 0 // no selection going on right now.
case root // i.e. the one wanting content
case branch // an intermediary. think of a folder when looking for a file
case leaf // like a file
}
enum ContentSelectability: Int {
case noSelections = 0
case oneSelection = 1
case multipleSelections = 2
}
protocol ContentSelection {
var selectedObjects: [NSManagedObject] { get set }
var selectionRole: ContentSelectionRole { get set }
var selectionStyle: ContentSelectability { get set }
func popToSelectionRootViewController() -> Bool
func willNavigateBack(from viewController: UIViewController)
}
Making the definition:
class ContentSelectableViewController<T: NSManagedObject> : UIViewController, ContentSelection { //... }
And then, refactored the original post, to get:
#discardableResult func popToSelectionRootViewController() -> Bool {
if let navCtrl = self.navigationController {
for viewController in navCtrl.viewControllers.reversed() {
if let vc = viewController as? ContentSelection {
if vc.selectionRole == .root {
vc.willNavigateBack(from: self)
navCtrl.popToViewController(viewController, animated: true)
return true
}
}
}
}
return false
}
I still don't quite understand the aspect of the language that makes it fail, but this solution works.
Protocol-based Programming seems to be more Swifty anyway...

Related

UITabBarController Shared Data Model - share & update model from anywhere

I'm using a TabBarcontroller type app and I'm using a shared model of the form:
enum WorkoutState {
case Stopped
case Started
case Paused
}
class BaseTBController: UITabBarController {
var workoutState: WorkoutState? = .Stopped
}
Currently all is working and I can access and update the variable across the different tabs using
let tabbar = tabBarController as! BaseTBController
if tabbar.workoutState = .Stop {
//do something
tabbar.workoutState = .Start
}
Now, the situation is that I seem to need to put this all over the place in my code. eg:
startRun()
resumeRun()
pauseRun()
Is there a better way to do this instead of putting
let tabbar = tabBarController as! BaseTBController
tabbar.workoutState = .Start
in each of the 3 functions?
You can always use protocol and default extension to achieve what you need
protocol HandleWorkStateProtocol where Self: UIViewController {
func updateWorkOutState(to: WorkoutState)
}
extension HandleWorkStateProtocol {
func updateWorkOutState(to state: WorkoutState) {
guard let tabBarController = self.tabBarController as? BaseTBController else { return }
tabBarController.workoutState = state
}
}
In all you view controller's that has these 3 methods (startRun, resumeRun, pauseRun) simply confirm to this protocol and call updateWorkOutState(to: with appropriate value to modify the status
class SomeTestViewController: UIViewController {
func startRun() {
self.updateWorkOutState(to: .Started)
}
func resumeRun() {
}
func pauseRun() {
self.updateWorkOutState(to: .Paused)
}
}
extension SomeTestViewController: HandleWorkStateProtocol {}
P.S
Case values of enum does not follow Pascal casing like Stopped instead it follows Camel casing stopped so change your enum values to
enum WorkoutState {
case stopped
case started
case paused
}

Access super class property from inherited class

I have a class, MyContainer, which has another custom class as a variable. This other class, MyInterface, is a view controller super class, which gets extended by two other child custom classes, MyVCA and MyVCB. The reason for this is because I have a bottom button which is used across both screens - only the content has to get updated every time, which I do programmatically. There is also a content manager which I use to know which of the two child classes to use, called MyContentManager.
The problem I am having is when I am going from a previous view controller to either MyVCA or MyVCB, because depending on which one it is, a certain task needs to be done or not. I am instantiating the view for MyVCB from the storyboard like so:
let vc = UIStoryboard(name: "Containers",
bundle: Bundle.main).instantiateViewController(withIdentifier:
"my_container") as! MyContainer
vc.contentManager = MyContentManager(type: .type_my_vc_a)
vc.shouldDoTask = true
self.navigationController?.pushViewController(vc, animated: true)
As can be seen I have created a flag, shouldDoTask, that needs to be set at this point (inside a previous view controller). But because it is set to the container super class, the children can not access it. So what needs to happen basically is that this flag needs to get propagated through the path MyContainer -> MyInterface -> MyVCA / MyVCB.
I have tried to use a property for the flag, in MyInterface:
private var _shouldDoTask: Bool = false
var shouldDoTask: Bool {
set { _shouldDoTask = newValue }
get { return _shouldDoTask }
}
And in MyContainer:
var content: MyInterface!
var shouldDoTask: Bool {
set {
if content != nil {
content.shouldDoTask = newValue
}
}
get {
return (content != nil)
? content.shouldDoTask
: false
}
}
Then in MyVCA / MyVCB I can access it like this:
class MyVCA: MyInterface {
func someMethod() {
if self.shouldDoTask {
// do task
}
}
}
This would work nicely, if it wasn't for the fact that the content is still nil when the flag gets set in the previous view controller. This is understandable because of course MyInterface has not been created yet. I am looking for a way past this. I have been thinking about a method that could get called in MyInterface's viewDidLoad method to set the flag, but I can't seem to figure it out.
Any ideas would be appreciated.
Something like this. Check if it helps.
protocol MyInterFaceDelegate {
func setValues()
}
MyInterFace {
let delegate : MyInterFaceDelegate
viewDidLoad() {
delegate.etValues()
}
}
extension MyContainer : MyInterFaceDelegate {
func setValues() {
content.shouldDoTast = self.shouldDoTast
}
}
When you create MyInterFace() after that you set the delegate
content = MyInterFace()
content.delegate = self

Swinject inject self's property into new UIViewController

Let's pretend we have an UITableViewController that on didSelectRowAtSection loads an instance of a class named i.e.: ClassToInject and it wants to inject it through a property injection because our ViewControllerToBePushed has a property of ClassToInject, that subsequently (because it's an UITabBarViewController) on the didSet callback it searches for all its viewControllers property that conforms to ClassToInjectPresentable simple as:
protocol ClassToInjectPresentable {
var property: ClassToInject { get set }
}
Until now, i would just do something like this:
func didSelectRowAtIndexPath {
let classToInject = self.loadClassToInjectFor(indexPath)
let tabBarViewController = SomeTabBarViewController()
tabBarViewController.property = classToInject
self.navigationController.push(tabBarViewController, animated: true)
}
And in SomeTabBarViewController ...
class SomeTabBarViewController: ClassToInjectPresentable {
var property: ClassToInject? {
didSet(newValue) {
self.viewControllers.filter{ $0 is ClassToInjectPresentable }.map{ $0 as! ClassToInjectPresentable }.forEach{ $0.property = newValue }
}
}
And everything should be get loaded nice and easy (but it's not). I've read about Swinject and this might be solved with it. I have seen lots of examples registering things like:
container.register(Animal.self) { _ in Cat(name: "Mimi") }
But I don't know if I can register some property that is loaded in self:
container.register(ClassToInjectInjector.self) { _ in
self.loadClassToInjectFor(indexPath) }
// And then
container.register(ClassToInjectPresentable.self) { _ in
SomeTabBarViewController() }
.initCompleted { r, p in
let tabBar = p as! SomeTabBarViewController
tabBar.property = r.resolve(ClassToInjectInjector.self)
// And lastly?
self.navigationController.pushViewController(tabBar, animated: true)
}
}
It is difficult to recommend proper solution without knowing details of your application, but here are some suggestions:
container.register(ClassToInjectInjector.self) { _ in
self.loadClassToInjectFor(indexPath)
}
In general, all register-ations should be done outside of your objects. Common setup ishaving one global Container, which contains all the registrations - you should look at them as instructions to build application objects without any implicit context. If your dependency needs to be created in the UITableViewController, you can pass it to resolve method as an argument:
container.register(ClassToInjectPresentable.self) { resolver, property in
let tabBar = SomeTabBarViewController()
tabBar.property = property
return tabBar
}
// in UItableVIewController
container.resolve(ClassToInjectPresentable.self,
argument: self.loadClassToInjectFor(indexPath))
Also this is usually a bad idea:
.initCompleted { r, p in
...
self.navigationController.pushViewController(tabBar, animated: true)
}
You should not mix application logic with DI - use Swinject purely for constructing your dependencies.
So your UITableViewController might look something like this:
func didSelectRowAtIndexPath {
let classToInject = self.loadClassToInjectFor(indexPath)
let tabBar = container.resolve(
SomeTabBarViewController.self, argument: loadClassToInjectFor(indexPath)
)
navigationController.push(tabBar, animated: true)
}
As for your TabBar and its view controllers: how do the UIViewControllers get into TabBar? Is it possible to do something like this?
class SomeTabBarViewController {
init(viewControllers: [UIViewController]) {
...
}
}
container.register(SomeTabBarViewController.self) { r, property
SomeTabBarViewController(viewControllers:[
r.resolve(MyViewController.self, argument: property),
r.resolve(MyViewController2.self, argument: property)
])
}
Finally I got the final answer by following the suggestions proposed.
public class Containers {
fileprivate init() { }
}
extension Containers {
static let activityPresentableContainer: Container = {
let container = Container()
container.register(ActivityTabBarController.self) { (r: Resolver, arg1: Activity) in
return ActivityTabBarController(activity: arg1)
}
container.register(ActivityPresentable.self) {
(r: Resolver, arg1: ActivityPresentableTabs, arg2: Activity) in
switch arg1 {
case .summary:
return ActivitySummaryViewController(activity: arg2)
case .detail:
return ActivityDetailPageViewController(activity: arg2)
case .map:
return ActivityMapViewController(activity: arg2)
case .charts:
return ActivityChartsViewController(activity: arg2)
case .strava:
return ActivityStravaViewController(activity: arg2)
}
}.inObjectScope(.transient)
return container
}()
With this approach, the named ActivityTabBarController gets instantiated always by the activityPresentableContainer using the following statement:
let controller = Containers.activityPresentableContainer.resolve(
ActivityTabBarController.self, argument: activity
)!
And then, each of the tabs inside the TabBarController gets instantiated using the required argument Activity and the type of tab itself using a .transient context. It resolves like this:
let activitySummary = Containers.activityPresentableContainer.resolve(
ActivityPresentable.self, arguments: ActivityPresentableTabs.summary, activity!
) as! UIViewController
This way I can generalize the tabs of the tab bar depending just on the information that they're using. If one of the tabs change in any moment, I can just change the registration, following the ActivityPresentable protocol.

How to show a view through protocol?

I want to detective networking state, when networking state changed, show a error view in current controller. But there is a problem by using protocol.
Here is the codes:
private func networkingDetection() {
//This is the detective method in appdelegate
try! reachability.startNotifier()
reachability.whenReachable = { [weak self] _ in
DispatchQueue.main.async {
self?.currentViewController().hideNetworkingErrorView()
}
}
reachability.whenUnreachable = { [weak self] _ in
DispatchQueue.main.async {
self?.currentViewController().showNetworkingErrorView()
}
}
}
And here is the protocol
protocol NetworkingErrorProtocol {
// I want to show the default view if there is no networkingErrorView, and
when declare a custom view in controller, show the custom view.
//var networkingErrorView: UIView? { get }
func showNetworkingErrorView()
func hideNetworkingErrorView()
}
extension UIViewController: NetworkingErrorProtocol {
func showNetworkingErrorView() {
}
func hideNetworkingErrorView() {
}
}
Anyone can tell me how to figure it out? It's really makes me crazy. Thanks a lot.
The issue with your setup is that conforming UIViewController to your protocol does not allow you to receive that call in your subclass. If you try to override the protocol function in your subclass you will get a compiler error: Declarations from extensions cannot be overridden yet
First off, a note about NotificationCenter. If you need multiple parts of your app to be notified of the change that would be a good way to go. If you only need to tell one controller, this is a classic usage for a delegate.
Here are two ways to get the desired functionality: using the delegate pattern and without.
Let's say Manager is the class where the monitoring is happening:
Using a delegate pattern
class Manager {
weak var networkDelegate : NetworkStatusListener?
func monitorNetworkStatus() {
var reachable = true;
if reachable {
// We can call the delegate directly
networkDelegate?.networkStatusChanged(.connected)
}
else {
networkDelegate?.networkStatusChanged(.disconnected)
}
}
}
And the same Manager without a delegate pattern. This would be the simplest fix for your current implementation issue.
class Manager {
func currentViewController() -> UIViewController { return vc }
func monitorNetworkStatus() {
var maybeAtListener = currentViewController()
// DON't SHIP THIS, but it can be helpful during development to make sure you didn't forget to conform one of your classes
assert(maybeAtListener is NetworkStatusListener, "Oops, did you mean to conform \(currentVC) to NetworkStatusListener")
var reachable = true;
if reachable {
// We can't be sure the controller conforms to the protocol but we can try
(maybeAtListener as? NetworkStatusListener)?.networkStatusChanged(.connected)
}
else {
(maybeAtListener as? NetworkStatusListener)?.networkStatusChanged(.connected)
}
}
}
Then for your view controller
class MyController : UIViewController, NetworkStatusDelegate {
func networkStatusChanged(_ status: NetworkStatus) {
switch status {
case .connected:
// Normal UI
break
case .disconnected:
// No network connect
break;
}
}
}
Also, not directly related to your question but for this example I used a slightly different approach to the protocol design that can be helpful for "status" oriented protocols. Having multiple functions can often become a little more tedious to conform to as protocols get larger.
enum NetworkStatus {
case connected
case disconnected
}
protocol NetworkStatusListener : class {
func networkStatusChanged(_ status: NetworkStatus)
}
Try using reachability class's NSNotificationCenter
add this in appdelegate's didFinishLaunchingWithOptions if you want for whole app
OR add in your specific viewcontroller if you want this in specific Viewcontroller
NotificationCenter.default.addObserver(self, selector:Selector(("checkForReachability:")), name: NSNotification.Name.reachabilityChanged, object: nil);
let reachability: Reachability = Reachability.forInternetConnection();
reachability.startNotifier();
This method called while network state changed .
func checkForReachability(notification:NSNotification)
{
let networkReachability = notification.object as! Reachability;
_ = networkReachability.currentReachabilityStatus()
// do yor additional work here
}

Storing UIViewController generic with a protocol as a property

Okay, so I'm pretty sure I'm overthinking this.
I am passing through a viewController that conforms to a protocol as a generic like so:
static func sortPage<T: UIViewController>(controller: T, err: NSError) where T: SortAlertDelegate { }
What I want to be able to do is store that controlleras a property so I can access all the functions UIViewController gives me and the functions thats the SortAlerDelegate gives me.
Any ideas?
You can't specify a type and protocol conformance for a property. You'll need to cast your property to the correct type whenever you want to use specific features. However, you can make this less painful with a bit of boilerplate:
let myProperty: UIViewController? = nil {
willSet(newValue) {
if (newValue as? SortAlertDelegate != nil) {
myProperty = newValue
} else {
myProperty = nil
}
}
}
This way, if you try to set the property to an object which doesn't conform to the protocol, the set will be aborted and the property will be set to nil.
You can also write read-only properties in order to get your property as the type you need at the moment:
let myPropertyAsViewController: UIViewController? {
get { return myProperty }
}
let myPropertyAsDelegate: SortAlertDelegate? {
get {
if let myProperty = myProperty {
return myProperty as! SortAlertDelegate
} else {
return nil
}
}
}
In general it is not possible unless you move the generic constraint on top of your class definition like:
class ViewController<T: UIViewController> : UIViewController where T: SortAlertDelegate {
let delegateController: T
}
But you can also make two references in your class like
class ViewController: UIViewController {
let controller: UIViewController
let delegate: SearchAlertDelegate
}
And then store the same object as two different references.

Resources