what does `#objc(ViewController) ` do? - ios

I have this swift code:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject : AnyObject]?) -> Bool {
...
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateInitialViewController()!
if (controller is InlineMainViewController ){
mainViewController = controller as! InlineMainViewController
}
window?.rootViewController = controller
window?.makeKeyAndVisible()
}
when my viewController has this attribute:
#objc(ViewController) // match the ObjC symbol name inside Storyboard
public class InlineMainViewController: UIViewController,
(controller is InlineMainViewController ) returns false
and when it doesn't have this attribute:
public class InlineMainViewController: UIViewController,
(controller is InlineMainViewController ) returns true
My Main.sotryboard is connected to InlineMainViewController
I took #objc(ViewController) from Google analytics github example

From Swift Type Compatibility
You can use the #objc attribute if your Swift class doesn’t inherit
from an Objective-C class, or if you want to change the name of a
symbol in your interface as it’s exposed to Objective-C code.
This code:
#objc(ViewController)
Will cause the class following this attribute to have the name in the parentheses when it is imported into Objective-C code through the generated header, as discussed in Importing Swift into Objective-C
To import Swift code into Objective-C from the same target
Import the Swift code from that target into any Objective-C .m file
within that target using this syntax and substituting the appropriate
name:
#import "ProductModuleName-Swift.h"

Related

How to Implement ViewController custom init using dependency injection and factory patterns?

I am trying to implement simple example using DI and factory.
ViewController.swift
class VIewController : UIViewController {
private let factory: ViewControllerFactory
init(with factory: Factory) {
self.factory = factory
super.init(nibName: nil, bundle: nil)
}
}
protocol ViewControllerFactory {
func makeViewController() -> ViewController
}
class DependencyContainer {
///
}
extension DependencyContainer: ViewControllerFactory {
func makeViewController() -> ViewController {
return ViewController(with: self)
}
}
AppDelegate.swift
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions:
[UIApplication.LaunchOptionsKey: Any]?) -> Bool {
self.window = UIWindow(frame: UIScreen.main.bounds)
let container = DependencyContainer()
let rootViewController = container.makeViewController()
self.window?.rootViewController = rootViewController
self.window?.makeKeyAndVisible()
return true
}
I have view controller designed in storyboard. How I can I correct my code to run app successfully?
App is crashing:
I am removing storyboard entry point from view controller, but the outlets are nil and it is crashing when I am using them.
initWithCoder is calling in case when I don't remove that entry point from storyboard.
The behavior you are describing is how storyboards work (ie they call initWithCoder). If you need your constructor to be called you can use a nib file then call your constructor which calls init(nibName:bundle:) and will correctly load the nib file. This works on current iOS versions.
The only way to get constructor injection with a storyboard is to use iOS 13 and use the new #IBSegueAction which will give you a coder that you can pass into your constructor and then call super initWithCoder.
In iOS 13, you can initialise a storyboard view controller as follows:
Add an initialiser to your ViewController class
let property: Property // Your injected property
required init?(coder: NSCoder, property: Property) {
self. property = property
super.init(coder: coder)
}
required init?(coder: NSCoder) { // You'll need to add this
fatalError("init(coder:) has not been implemented")
}
In your factory class:
let viewController = storyboard.instantiateViewController(identifier: "ViewController") { coder in
ViewController(coder: coder, property: <injected property>)
}
There are different types of dependency injection. You're currently trying to use constructor-based dependency injection, which unfortunately doesn't really work with storyboards, since they need to initialise with a decoder. iOS 13 does introduce some additional functionality which will make this approach possible, but for the moment, you could use setter-based dependency injection instead.
Something like the following:
class ViewController: UIViewController {
var factory: ViewControllerFactory!
}
protocol ViewControllerFactory {
func makeViewController() -> ViewController
}
class DependencyContainer {
let storyboard: UIStoryboard = UIStoryboard(name: "Storyboard", bundle: Bundle.main)
}
extension DependencyContainer: ViewControllerFactory {
func makeViewController() -> ViewController {
guard let vc = storyboard.instantiateViewController(withIdentifier: "ViewController") as? ViewController else {
fatalError("Unrecognised viewController")
}
vc.factory = self
return vc
}
}

Unable to call method of ViewController()

I am simply trying to make a method in the ViewController class and be able to call it.
Here is my code (I note the 2 ways I tried calling it, and the errors I got):
import UIKit
class ViewController: UIViewController{
func sayHi(name: String){
print("Hi \(name)")
}
}
/*
let viewcontroller = ViewController()
viewcontroller.sayHi(name: "Bob")
*/
//Error: Expressions are not allowed at the top level
/*
ViewController.sayHi(name: "Bob")
*/
//Error: Expressions are not allowed at the top level
//Error: Instance member 'sayHi' cannot be used on type 'ViewController'; did you mean to use a value of this type instead?
So as you can see in the commenting, I tried to call sayHi as a type method and as an instance method. Neither worked. I will ultimately create a function that can take input from a text input, and manipulate it. Is ViewController.swift even the right file to be doing this? If so, how do I call a method that I have defined?
There will be this delegate in appDelegate which will be called when you app is launched. Create your viewController there and add it to the window.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
window?.makeKeyAndVisible()
let viewController = ViewController()
window?.rootViewController = viewController
viewController.sayHi()
return true
}
This will work on Playground, but not on Xcode.
Xcode's code is compiled and then you have an app. The first point where a call happens is AppDelegate and from there your first controller and its methods are initialised. Nothing outside a class will be executed.
Use playground for tests or any other online swift playground.
If you want to run sayHi immediately, put it in viewDidLoad and load the app. Delete all further code outside of the class before building again:
override func viewDidLoad() {
super.viewDidLoad()
sayHi()
}
You can create a instance of controller inside a function or a block
when you are working in a class or Xcode projects in Xcode Playground your way of accessing the function sayHi(name: String) in
ViewController works.
For Xcode projects try the following
import UIKit
class ViewController: UIViewController{
func sayHi(name: String){
print("Hi \(name)")
}
}
class SecondViewController: UIViewController{
override func viewDidLoad() {
super.viewDidLoad()
let viewcontroller = ViewController()
viewcontroller.sayHi(name: "Bob")
}
}
When you initialise the SecondViewController you can access the ViewController()
To execute the function sayHi(name: String) immediately when the
ViewController() is initialised you can call it in viewDidLoad() or in
func viewWillAppear()
import UIKit
class ViewController: UIViewController{
override func viewDidLoad(_ animated: Bool) {
super.viewDidLoad(animated)
//Call the function hear
sayHi(name: "Bob")
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// or call hear
sayHi(name: "Bob")
}
func sayHi(name: String){
print("Hi \(name)")
}
}

How to set variable value through AppDelegate in Swift?

I have UIViewController in Main.storyboard and set it's identifier to settings. I reference Main.storyboard and get the view controller in AppDelegate and then set the value of a variable which is located in view controller but it returns nil.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
let mainStoryboardIpad : UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
if let settingViewController = mainStoryboardIpad.instantiateViewController(withIdentifier: "settings") as UIViewController? {
let settingVC = settingViewController as! SettingViewController
settingVC.moc = persistendContainer.viewContext
}
return true
}
And I have following variable in SettingViewController:
var moc: NSManagedObjectContext!
I confirmed that I am able to get Main.storyboard, SettingViewController and persistentContainer.viewContext has value.
You are instantiating a new settings UIViewController successfully, but then nothing happens with it after that. Later on your app will create a Settings viewController through the storyboard, but that will be a different one.
The better way to do this is to create an accessible place for your viewController to access the moc variable rather than trying to pass it in in advance. This is usually done for you if you let Xcode create the project with CoreData set up. You can say (UIApplication.shared.delegate as! AppDelegate).managedObjectContext from any viewController in your app and get a pointer to that moc.

Typhoon Dependency Injection and Swift 3: Appdelegate isn't AnyObject

This code works with Typhoon Dependency Injection library (Obj-C) in Swift 2.3 but doesn't in Swift 3:
AppDelegate:
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var rootViewController: RootViewController?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
self.window = UIWindow(frame: UIScreen.main.bounds)
self.window?.rootViewController = self.rootViewController
self.window?.makeKeyAndVisible()
return true
}
...
}
Application Assembly:
public class ApplicationAssembly: TyphoonAssembly {
public dynamic func config() -> AnyObject {
return TyphoonDefinition.withConfigName("Configuration.plist")
}
public dynamic func appDelegate() -> AnyObject {
return TyphoonDefinition.withClass(AppDelegate.self) {
(definition) in
definition!.injectProperty(#selector(ApplicationAssembly.rootViewController), with: self.rootViewController())
}
}
...
}
However the following error is displayed in ApplicationAssembly for any Swift 3 file expected to return 'AnyObject':
"No 'withClass' candidates produce the expected contextual result type 'AnyObject'
Might anyone have an insight into the incompatibility of the Obj-c Typhoon code base with Swift 3?
Screen capture of error line
You may want to switch the return type from AnyObject to Any.
The withClass function returns an id type in Objective-C, see the source code:
+ (id)withClass:(Class)clazz block:(TyphoonBlockDefinitionInitializerBlock)block;
The id type used to be mapped to AnyObject in Swift 2, but in Swift 3 it's mapped to Any for increased flexibility. You can read more about this change here.

Loading Initial View from Framework

Is it possible to create an application that loads its initial view from a storyboard in a framework?
Let's say I have a framework, titled MyCoolFramework. This framework has a storyboard called Home.storyboard that is the initial view of an application.
Is there a way to load it into the application at launch?
My AppDelegate looks like this:
import UIKit
import MyCoolFramework
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
let bundle = NSBundle(identifier: "com.myprojects.MyCoolFramework")
let homeStoryboard = UIStoryboard(name: "Home", bundle: bundle)
let initialView = homeStoryboard.instantiateInitialViewController()
self.window?.rootViewController = initialView
self.window?.makeKeyAndVisible()
return true
}
}
It seems that at this point in the application the application does not have access to the framework or its contents. Anyone know of the proper way of doing this? This is the companion test-app for a framework I'm developing.
I have deleted the main storyboard directive in the info.plist
I think its likely to be the path to your framework isn't correct.
You can try this (sorry Objective-C I'm sure you can convert to Swift)
[NSBundle bundleForClass: a name of your class in the framework]
Or if that doesn't work, add a method to your framework which returns the path of its own bundle and the code above as the implementation of that method.
NSBundle:mainBundle and NSBundle:bundleForClass will return different paths when called from within a framework (though if called from a library they will be the same). So experiment until you get the correct path for your storyboard.

Resources