Access super class property from inherited class - ios

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

Related

present a ViewController from a Swift class derived from an NSObject?

This project was written in Objective C and a bridging header and Swift files were added so they can be used at run time. When the app starts, Initializer called in Mock API client is printed in the debugger. Is it possible to present a ViewController from the initializer?
Xcode Error:
Value of type 'MockApiClient' has no member 'present'
//MockApiclient.Swift
import Foundation
class MockApiClient: NSObject
{
override init ()
{
print("Initializer called in Mock API client")
if isLevelOneCompleted == false
{
print("It's false")
let yourVC = ViewController()
self.present(yourVC, animated: true, completion: nil)
} else
{
print("It's true")
}
}
var isLevelOneCompleted = false
#objc func executeRequest()
{
print("The execute request has been called")
isLevelOneCompleted = true
if isLevelOneCompleted {
print("It's true")
} else {
//do this
}
}
}
Update - ViewController.m
// prints "The execute request has been called" from the debugger window
- (void)viewDidLoad {
[super viewDidLoad];
MockApiClient *client = [MockApiClient new];
[client executeRequest];
}
You can't call present(_:animated:completion) because it is a method of UIViewController, not NSObject.
Why not pass a viewController reference to the MockApiClient to present on instead like so. Be sure to check Leaks or Allocations on instruments to avoid the client retaining the controller.
class MockApiClient: NSObject {
var referencedViewController: UIViewController?
override init() {
let presentableViewController = ViewController()
referencedViewController.present(presentableViewController, animated: true, completion: nil)
}
deinit {
referencedViewController = nil
}
}
let apiClient = MockApiClient()
apiClient.referencedViewController = // The view controller you want to present on
Assuming you're using UIKit, you'll have to present the view controller from the nearest available attached view controller. If you know for certain that no other view controllers would currently be presented then you can safely present from the root view controller:
UIApplication.shared.keyWindow?.rootViewController?.present(someViewController, animated: true, completion: nil)
This concept of attached and unattached/detached view controllers is never officially explained but the infamous UIKit warning of presenting view controllers on detached view controllers is real. And the workaround is finding the nearest available attached view controller, which at first (when nothing is currently being presented) is the root view controller (of the window). To then present an additional view controller (while one is currently being presented), you'd have to present from that presented view controller or its nearest parent view controller if it has children (i.e. if you presented a navigation view controller).
If you subclass UIViewController, you can add this functionality into it to make life easier:
class CustomViewController: UIViewController {
var nearestAvailablePresenter: UIViewController {
if appDelegate.rootViewController.presentedViewController == nil {
return appDelegate.rootViewController
} else if let parent = parent {
return parent
} else {
return self
}
}
}
Then when you wish to present, you can simply do it through this computed property:
nearestAvailablePresenter.present(someViewController, animated: true, completion: nil)

Conditionally cast of generic view controller fails

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...

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.

iOS: Unit test with a void func in Swift

I want to test this method that doesn't return a value but I want to check if works fine.
Can you give me some suggestions?
func login() {
if Utility.feature.isAvailable(myFeat) {
if self.helper.ifAlreadyRed() {
self.showWebViewController()
} else {
let firstVC = FirstViewController()
self.setRootController(firstVC)
}
} else {
let secondVC = SecondViewController()
self.setRootController(secondVC)
}
}
so what's the best approach to apply unit test here?
Testing side effects is one approach. But for an example like the code in question, I actually prefer a subclass-and-expect approach.
Your code has three different paths.
If feature is available and already red, show web view controller.
If feature is available and not already red, show first view controller.
If feature is not available, show second view controller.
So assuming this login() function is part of FooViewController, one possibility is writing tests that follow this format:
func testLoginFeatureAvailableAndNotAlreadyRed() {
class TestVC: FooViewController {
let setRootExpectation: XCTExpectation
init(expectation: XCTExpectation) {
setRootExpectation = expectation
super.init()
}
override func setRootController(vc: UIViewController) {
defer { setRootExpectation.fulfill() }
XCTAssertTrue(vc is FirstViewController)
// TODO: Any other assertions on vc as appropriate
// Note the lack of calling super here.
// Calling super would inaccurately conflate our code coverage reports
// We're not actually asserting anything within the
// super implementation works as intended in this test
}
override func showWebViewController() {
XCTFail("Followed wrong path.")
}
}
let expectation = expectationWithDescription("Login present VC")
let testVC = TestVC(expectation: expectation)
testVC.loadView()
testVC.viewDidLoad()
// TODO: Set the state of testVC to whatever it should be
// to expect the path we set our mock class to expect
testVC.login()
waitForExpectationsWithTimeout(0, handler: nil)
}

Retrieve var value from another class

i have those two classes and i want to retrieve a value from one to another:
class SearchUserViewController:UIViewController{
var selectedUser: String!
#IBAction func btn_accept(sender: AnyObject) {
selectedUser = "Norolimba"
self.dismissViewControllerAnimated(true, completion: nil)
}
}
I'm saving the value to "selectedUser" var, then i want to check the value from this class:
class CalendarViewController: UIViewController {
override func viewDidAppear(animated: Bool) {
let vc : SearchUserViewController! = self.storyboard.instantiateViewControllerWithIdentifier("searchView") as SearchUserViewController
println("\(vc.selectedUser)")
if vc.selectedUser == nil {
self.requestData("team")
}else{
self.requestData("user")
}
}
}
But when i println the value "vc.selectedUser" the value is nil. So what can i do here to catch it from the other class and don't get a nil value?
searchView is here:
Hope you can help me.
Thanks
When you use instantiateViewControllerWithIdentifier(), you're not accessing the view controller that was being displayed on the screen, nor are you accessing the controller that has potentially been automatically instantiated by Interface Builder.
What you're doing is instantiating (hence the name) a new instance of that controller. So the instance variable selectedUser of that new instance is going to be nil.
What you should do is probably provide a callback to your SearchUserViewController when you display it, so that it can notify the view that presented it when a user is picked.
Alternatively, you can use the parentViewController property of UIViewController in cases where you (the view controller) are being presented modally to access the view controller that presented you. So in your SearchUserViewController, when it's being dismissed, it can access self.parentViewController, which should be a CalendarViewController, and call a method or set a property.
(But for the modal controller to assume who its parent is, is a bit of a code smell. I recommend using a callback or delegate of some sort.)
Edit: An example of using the completion callback:
class CalendarViewController : UIViewController {
public func displayUserSelectionController() {
let suvc : SearchUserViewController = ... (how are you displaying it?) ...
self.presentViewController(suvc, animated:true, completion: {
// The SUVC is hiding
if let user = suvc.selectedUser {
self.requestData("team")
} else {
self.requestData("user")
}
})
}
...
}

Resources