I have this function that I use all over my app, and it would be nice to create a global function:
class CustomReportVC: UIViewController, UIAdaptivePresentationControllerDelegate, UIPopoverPresentationControllerDelegate {
func showPicker(pickerValues:[String], field:UITextField) -> AnyPickerVC{
//Set up modal
let storyboard = UIStoryboard(name: "Popovers", bundle: nil)
let modal = storyboard.instantiateViewControllerWithIdentifier("AnyPickerModal") as! AnyPickerVC
modal.modalPresentationStyle = UIModalPresentationStyle.Popover
let pc = modal.popoverPresentationController
pc?.permittedArrowDirections = .Down
pc?.sourceView = field
pc?.sourceRect = field.bounds
modal.preferredContentSize = CGSizeMake(300,180)
pc?.delegate = self
//Pass in data
modal.data = pickerValues
//Set the value from within the picker controller
modal.passDataToParent = { (value) in
field.text = value
}
return modal
}
//Required for the popover
func adaptivePresentationStyleForPresentationController(controller: UIPresentationController) -> UIModalPresentationStyle {
return .None
}
}
The issue I'm running into comes with pc?.delegate = self. Since CustomReportVC conforms to UIPopoverPresentationControllerDelegate, this works fine.
But once I attempt to create this as a global function outside a class that conforms to this protocol, I get an error:
func globalShowPicker(pickerValues:[String], field:UITextField, controller:UIViewController) -> AnyPickerVC{
//...
pc?.delegate = controller //<-- ( ! ) Type UIViewController does not conform to UIPopoverPresentationControllerDelegate
}
Whether I make controller a UIViewController or AnyObject, it doesn't conform. Is there a way to pass in the protocol conformity to the global function somehow?
Any idea how I can pull this off? Thanks. :)
Make your global function generic to specify that it only works for certain kinds of UIViewControllers. In this example, T can take the value of any UIViewController type which also conforms to the other protocols listed.
func globalShowPicker< T: UIViewController where
T: UIPopoverPresentationControllerDelegate,
T: UIAdaptivePresentationControllerDelegate >
(pickerValues:[String], field:UITextField, controller: T) -> AnyPickerVC
{
//...
pc?.delegate = controller
return blah
}
It does get kinda long, and I haven't figured out the best way to indent all the constraints. But it works.
Try adding a making a new class that inherits from both of them. Like this.
class PopoverController: UIViewController, UIPopoverPresentationControllerDelegate {
}
Then, switch the function to look like this.
func globalShowPicker(pickerValues:[String], field:UITextField, controller: PopoverController) -> AnyPickerVC{
//...
pc?.delegate = controller
}
You can pass the delegate as a parameter in the function like this:
class CustomReportVC: UIViewController, UIAdaptivePresentationControllerDelegate, UIPopoverPresentationControllerDelegate {
class func showPicker(pickerValues:[String], field:UITextField, delegate: UIPopoverPresentationControllerDelegate) -> UIViewController {
//Set up modal
let storyboard = UIStoryboard(name: "Popovers", bundle: nil)
let modal = storyboard.instantiateViewControllerWithIdentifier("AnyPickerModal") as! UIViewController
modal.modalPresentationStyle = UIModalPresentationStyle.Popover
let pc = modal.popoverPresentationController
pc?.permittedArrowDirections = .Down
pc?.sourceView = field
pc?.sourceRect = field.bounds
modal.preferredContentSize = CGSizeMake(300,180)
pc?.delegate = delegate
//Pass in data
modal.data = pickerValues
//Set the value from within the picker controller
modal.passDataToParent = { (value) in
field.text = value
}
return modal
}
//Required for the popover
func adaptivePresentationStyleForPresentationController(controller: UIPresentationController) -> UIModalPresentationStyle {
return .None
}
}
You have at the end to have an instance of a view controller that does conform to the protocol, but that way you will have the function global just like you want and pass self pointer in the view controller that does conform to the protocol "UIPopoverPresentationControllerDelegate":
CustomReportVC.showPicker(pickerValues:....., delegate: self)
Something like this?
self.presentViewController(pc, animated: true, completion: nil)
Btw, if you're doing Universal you can not present iPad UIActivityViewController like iPhone. You need to present it in a popover as per the design guidelines suggested by Apple.
or as an example
#IBAction func shareButtonClicked(sender: UIButton)
{
let textToShare = "Text"
if let myWebsite = NSURL(string: "http://www.example.com/")
{
let objectsToShare = [textToShare, myWebsite]
let activityVC = UIActivityViewController(activityItems: objectsToShare, applicationActivities: nil)
var nav = UINavigationController(rootViewController: activityVC)
nav.modalPresentationStyle = UIModalPresentationStyle.Popover
var popover = nav.popoverPresentationController as UIPopoverPresentationController!
activityVC.preferredContentSize = CGSizeMake(500,600)
popover.sourceView = self.view
popover.sourceRect = CGRectMake(100,100,0,0)
self.presentViewController(nav, animated: true, completion: nil)
}
}
class MyViewController : UIViewController, UIPopoverPresentationControllerDelegate {
//code here
}
Related
I am working on building framework for a widget. I have two files as follows:
widgetBuilder.swift and
webWidget.swift
widgetBuilder.swift is file with getters and setters that takes certain values from the app that is going to use this widget framework.
Code from widgetBuilder.swift
import Foundation
class WidgetBuilder {
private var userId: String!
private var emailId: String!
public func setuserId(userId: String) -> WidgetBuilder{
self.userId = userId
return self
}
public func setEmailId(emailId: String) -> WidgetBuilder{
self.emailId = emailId
return self
}
public func build() -> WidgetBuilder{
// Wanted to load the webview from here
}
}
Once the initialization is done I would call the build function, I wanted to load the ViewController of webWidget.swift
Code from webWidget.swift
class webWidget: UIViewController, WKUIDelegate, WKNavigationDelegate {
var webView: WKWebView!
override func loadView() {
webView = WKWebView()
webView.uiDelegate = self
webView.navigationDelegate = self
view = webView
self.loadWebView()
}
public func loadWebView(){
let url = URL(string: "https://www.google.com")!
webView.load(URLRequest(url: url))
webView.allowsBackForwardNavigationGestures = true
}
public func loadWidgetScreen() {
//Something is not correct here
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "WidgetController")
self.present(controller, animated: true, completion: nil)
}
}
How do I load the webWidget view from the widgetBuilder.swift and pass some data along ?
So this is how I solved , Posting it, might be helpful for somebody looking to load a ViewController from a swift file.
Once I call the the build() from my swift file , it has to load the view,
Added a var that gets the ViewController instance from the parent view that wants to load this widget.
Inside the widgetBuilder.swift
private var clientView: UIViewController?
public init(_ viewController:UIViewController){
self.clientView = viewController;
}
Then,
public func build(){
// Widget() creates ViewController instance , this viewcontroller is mapped to a widget.xib file
let controller: UIViewController = Widget() // Creating the instance of the view class
self.clientView?.addChild(controller) // self.clientView is the ViewController instance that wants to load widget view
self.clientView?.view.addSubview(controller.view)
controller.view.frame = (clientView?.view.bounds)!
controller.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
controller.didMove(toParent: clientView)
}
In webWidget.swift
replace your loadWidgetScreen() method with below
public func loadWidgetScreen() {
if var topController = UIApplication.sharedApplication().keyWindow?.rootViewController {
while let presentedViewController = topController.presentedViewController {
topController = presentedViewController
}
// topController should now be your topmost view controller
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "WidgetController")
self.present(controller, animated: true, completion: nil)
}
else {
print("topViewController not found")
}
}
I am attempting to write a func that 1) instantiates a subclass of UIViewController and 2) pushes into the navigation controller of the caller UIViewController.
So far, I have this:
func pushAnyViewController<T>(viewController:T, storyboardName:String) {
// Instantiate the view controller of type T
guard let nextViewController = UIStoryboard(name: storyboardName, bundle: nil).instantiateViewController(withIdentifier: String(describing: T.self)) as? T else {
return
}
viewController.navigationController.pushViewController(nextViewController, animated: true)
}
This produces error
Value of type 'T' has no member 'navigationController'
I am not sure if somehow I should say that T will always be a subclass of UIViewController. If that is the case, it's not clear where I do that. For this, I thought about:
func pushAnyViewController<T>(viewController:T & UIViewController, storyboardName:String)
but that produces errors:
Generic parameter 'T' is not used in function signature
Non-protocol, non-class type 'T' cannot be used within a protocol-constrained type
You need to identify that T is a vc with <T:UIViewController>
func pushAnyViewController<T:UIViewController>(viewController:T, storyboardName:String) {
guard let nextViewController = UIStoryboard(name: storyboardName, bundle: nil).instantiateViewController(withIdentifier: String(describing: T.self)) as? T else { return }
viewController.navigationController?.pushViewController(nextViewController, animated: true)
}
The top answer didn't suit me, I needed a more generic solution. First, I needed the option to pass data, second, I wanted to have a generic function that can push or present VC, and last, I wanted that my generic function can be called from anywhere, not just from the UIViewController, that's why an extension of UIViewController didn't suit me.
I decided to create a struct with simple init, and two public methods so that I can create the copy anywhere and call those methods.
struct Navigator {
// MARK: - DisplayVCType enum
enum DisplayVCType {
case push
case present
}
// MARK: - Properties
private var viewController: UIViewController
static private let mainSBName = "Main"
// MARK: - Init
init(vc: UIViewController) {
self.viewController = vc
}
// MARK: - Public Methods
public func instantiateVC<T>(withDestinationViewControllerType vcType: T.Type,
andStoryboardName sbName: String = mainSBName) -> T? where T: UIViewController {
let storyBoard: UIStoryboard = UIStoryboard(name: sbName, bundle: nil)
let destinationVC = storyBoard.instantiateViewController(withIdentifier: String(describing: vcType.self))
return destinationVC as? T
}
public func goTo(viewController destinationVC: UIViewController,
withDisplayVCType type: DisplayVCType = .present,
andModalPresentationStyle style: UIModalPresentationStyle = .popover) {
switch type {
case .push:
viewController.navigationController?.pushViewController(destinationVC, animated: true)
case .present:
destinationVC.modalPresentationStyle = style
viewController.present(destinationVC, animated: true, completion: nil)
}
}
}
and example call in some VC, with passing string after push:
class SomeVC: UIViewController {
var navigator: Navigator?
override func viewDidLoad() {
super.viewDidLoad()
navigator = Navigator(vc: self)
}
func pushVC() {
guard let vc = navigator?.instantiateVC(withDestinationViewControllerType: VC1.self) else { return }
vc.someString = "SOME STRING TO BE PASSED"
navigator?.goTo(viewController: vc, withDisplayVCType: .push)
}
func presentVC() {
guard let vc = navigator?.instantiateVC(withDestinationViewControllerType: TableViewController.self) else { return }
navigator?.goTo(viewController: vc, withDisplayVCType: .present)
}
}
With Help of some keyword.
enum Storyboard : String {
case Main
}
func viewController(_ viewController: UIViewController.Type) -> some UIViewController {
return UIStoryboard(name: self.rawValue, bundle: nil).instantiateViewController(withIdentifier: String(describing: viewController.self))
}
Is there a way to instantiate - set - push a view controller using a helper method like this but in the same time avoiding downcasting?
func pushController(id: String, setup: (_ vc: UIViewController) -> ()) {
if let vc = storyboard?.instantiateViewController(withIdentifier: id) {
setup(vc)
navigationController?.pushViewController(vc, animated: true)
}
}
// usage
pushController(id: "Cars") { vc in
(vc as! CarsVC).brand = "BMW"
}
// ...want to avoid downcasting
vc.brand = "BMW"
The most elegant solution I could think of is using generics, like this (playground)-example:
import UIKit
extension UIViewController {
func pushController<T:UIViewController> (id: String, setup: (_ vc: T) -> ()) {
if let vc = self.storyboard?.instantiateViewController(withIdentifier: id) as? T {
setup(vc)
self.navigationController?.pushViewController(vc, animated: true)
}
}
}
class ViewControllerA:UIViewController {}
class ViewControllerB:UIViewController {
var bValue:Int = 0
}
let vcA = ViewControllerA();
vcA.pushController(id: "B") {
(vc:ViewControllerB) in
vc.title = "view controller b"
vc.bValue = 42;
}
I would have preferred calling pushController with an explicit generic type, but unfortunatley this is not supported by Swift 3:
vcA.pushController<ViewControllerB>(id: "B") { // Error: cannot explicitly specialize a generic function
vc in
vc.title = "view controller b"
vc.bValue = 42;
}
I don't think you can avoid downcasting, but you can make it less painful:
func pushController<VC: UIViewController>(id: String, setup: (_ vc: VC) -> ()) {
if let vc = storyboard?.instantiateViewController(withIdentifier: id) as? VC {
setup(vc)
navigationController?.pushViewController(vc, animated: true)
}
}
// usage
pushController(id: "Cars") { (vc: CarsVC) in
vc.brand = "BMW"
}
Not tested, so there might be minor issues.
EDIT: I should note that this fails silently when the wrong type is used with an ID. You may want to add an else after the if to handle this.
I have a profile class and settings class
profile class contains an internal function
class Profile: UIViewController,UIImagePickerControllerDelegate,UINavigationControllerDelegate {
internal func profileSelectFromGallery(sender: Profile){
let myPickerController = UIImagePickerController()
myPickerController.delegate = sender;
myPickerController.sourceType =
UIImagePickerControllerSourceType.PhotoLibrary
sender.presentViewController(myPickerController, animated:true, completion: nil)
}
}
I want to use profileSelectFromGallery in setting class and I have two tries below
class SettingsVC: UITableViewController {
// I will call this private function on a click events
private func selectFromGallery(){
// let profile = Profile()
// profile.profileSelectFromGallery(self)
Profile.profileSelectFromGallery(self)
}
}
The above codes results into Cannot convert value of type 'SettingsVC' to expected argument type 'Profile' since profileSelectFromGallery needs a parameter of a class Profile so what i want to do is change sender so that i can use it from any of my class and not just my Profile class.
So the problem is that you can't convert a SettingsVC into a Profile. If you look at the method signature you'll see it's expecting a Profile:
internal func profileSelectFromGallery(sender: Profile)
You are trying to pass in a SettingVC in selectFromGallery()
Inside profileSelectFromGallery you want the sender to be both a UIViewController and a UIImagePickerControllerDelegate. There's a couple ways you could do this:
The simplest is to change the method signature. You'd do something like this:
internal func profileSelectFromGallery(sender: UIImagePickerControllerDelegate){
guard let vc = sender as? UIViewController else {
return
}
let myPickerController = UIImagePickerController()
myPickerController.delegate = sender;
myPickerController.sourceType =
UIImagePickerControllerSourceType.PhotoLibrary
vc.presentViewController(myPickerController, animated:true, completion: nil)
}
Theres 2 main things here: sender is changed to the proper delegate method, and theres a guard statement to cast it to a VC for the presentViewController call.
The much more awesome way to do this is to use protocol extensions!
extension UIImagePickerControllerDelegate where Self: UIViewController, Self: UINavigationControllerDelegate {
func profileSelectFromGallery() {
let myPickerController = UIImagePickerController()
myPickerController.delegate = self
myPickerController.sourceType = UIImagePickerControllerSourceType.PhotoLibrary
self.presentViewController(myPickerController, animated:true, completion: nil)
}
}
Basically what I'm doing here is adding a method for every UIImagePickerControllerDelegate thats also an UIViewController and an UINAvigationControllerDelegate. This means that I can call it on both Profile and SettingVC (once you add the necessary delegates to SettingVC). All you would need to do is:
let profile = Profile()
profile.profileSelectFromGallery()
let settingVC = SettingVC()
settingVC.profileSelectFromGallery()
Declare new protocol as:
protocol PickerProtocol : UIImagePickerControllerDelegate, UINavigationControllerDelegate {
}
Now your Profile class will look like:
class Profile: UIViewController, PickerProtocol {
//Option 1
internal func profileSelectFromGallery(contoller: UIViewController, pickerProtocol: PickerProtocol){
let myPickerController = UIImagePickerController()
myPickerController.delegate = pickerProtocol
myPickerController.sourceType =
UIImagePickerControllerSourceType.PhotoLibrary
contoller.presentViewController(myPickerController, animated:true, completion: nil)
}
//Option 2
internal func profileSelectFromGalleryOption2(sender : UIViewController? ) {
var viewContoller : UIViewController = self
if let unwrappedSender = sender {
viewContoller = unwrappedSender
}
let myPickerController = UIImagePickerController()
if let pickerProtocol = viewContoller as? PickerProtocol {
myPickerController.delegate = pickerProtocol
} else {
myPickerController.delegate = self //Assign self as default
}
myPickerController.sourceType = UIImagePickerControllerSourceType.PhotoLibrary
viewContoller.presentViewController(myPickerController, animated:true, completion: nil)
}
}
class SettingsVC1: UITableViewController {
// I will call this private function on a click events
private func selectFromGallery(){
let profile = Profile()
profile.profileSelectFromGallery(self, pickerProtocol:profile)
profile.profileSelectFromGalleryOption2(self)
//Or
profile.profileSelectFromGalleryOption2(nil)//profile itself delegate and presenting controller
}
}
// OR
class SettingsVC2: UITableViewController, PickerProtocol {
// I will call this private function on a click events
private func selectFromGallery(){
let profile = Profile()
profile.profileSelectFromGallery(self, pickerProtocol:self)
profile.profileSelectFromGalleryOption2(self)
//Or
profile.profileSelectFromGalleryOption2(nil)//profile itself delegate and presenting controller
}
}
I would use POP (protocol oriented programming) like this:
protocol SelectorProtocol: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
}
extension SelectorProtocol where Self: UIViewController {
func profileSelectFromGallery() {
let myPickerController = UIImagePickerController()
myPickerController.delegate = self;
myPickerController.sourceType = UIImagePickerControllerSourceType.PhotoLibrary
self.presentViewController(myPickerController, animated:true, completion: nil)
}
}
class Profile: UIViewController,UIImagePickerControllerDelegate,UINavigationControllerDelegate, SelectorProtocol {
func foo() {
profileSelectFromGallery()
}
}
class SettingsVC: UITableViewController, SelectorProtocol {
// I will call this private function on a click events
private func selectFromGallery(){
profileSelectFromGallery()
}
}
You are trying to statically call profileSelectFromGallery: even though it is an instance method.
Try changing the method definition to:
internal static func profileSelectFromGallery(sender: Profile){
As for being able to use any class as a delegate, create a custom Protocol and ensure that the sender conforms to this protocol. See here (specifically the heading titled Protocols) for more information: http://www.raywenderlich.com/115300/swift-2-tutorial-part-3-tuples-protocols-delegates-and-table-views
perhaps the following will work:
class SettingsVC: UITableViewController {
// I will call this private function on a click events
private func selectFromGallery(){
let prof = Profile()
prof.profileSelectFromGallery(prof)
}
}
I have a UITableViewController subclass called LogbookFormTVC that conforms to UIPopoverPresentationControllerDelegate. In this class I have a function that creates and shows a popover:
// --------------------
// LogbookFormTVC.swift
// --------------------
class LogbookFormTVC: UITableViewController, UIAdaptivePresentationControllerDelegate, UIPopoverPresentationControllerDelegate {
#IBAction func tapShowPopover(sender: AnyObject) {
//Tap to show the popover
self.presentViewController(showAircraftPicker(), animated: true, completion: nil)
}
//Build the popover
func showAircraftPicker() -> UIViewController{
//Set up modal
let storyboard = UIStoryboard(name: "Popovers", bundle: nil)
var aircraftModal = storyboard.instantiateViewControllerWithIdentifier("AircraftModal") as! AircraftPickerVC
let pc = aircraftModal.popoverPresentationController
pc?.sourceView = self.view
pc?.delegate = self
return aircraftModal
}
}
I want to move this showAircraftPicker() function and make it available anywhere in my app, so I move it to another file like this:
// --------------------
// SomeWhereElse.swift
// --------------------
//This works
func showAircraftPicker(controller: LogbookFormTVC) -> UIViewController{
//Set up modal
let storyboard = UIStoryboard(name: "Popovers", bundle: nil)
var aircraftModal = storyboard.instantiateViewControllerWithIdentifier("AircraftModal") as! AircraftPickerVC
let pc = aircraftModal.popoverPresentationController
pc?.sourceView = self.view
pc?.delegate = self
return aircraftModal
}
Note how I have to set the type of controller to LogbookFormTVC in order for its protocol conformity to come in with it. But I want this function to work with any class (that conforms to the right protocol, of course).
So doing this doesn't work:
func showAircraftPicker(controller: AnyObject) -> UIViewController{
//Set up modal
let storyboard = UIStoryboard(name: "Popovers", bundle: nil)
var aircraftModal = storyboard.instantiateViewControllerWithIdentifier("AircraftModal") as! AircraftPickerVC
let pc = aircraftModal.popoverPresentationController
pc?.sourceView = self.view
pc?.delegate = self <-- !!! Type AnyObject does not conform to protocol UIPopoverPresentationControllerDelegate !!!
return aircraftModal
}
How can I make this function work with any class and pass on that class's protocol conformity?
You could try to create and extension for UIViewController like this:
extension UIViewController {
func showAircraftPicker(delegate: UIPopoverPresentationControllerDelegate) {
let storyboard = UIStoryboard(name: "Popovers", bundle: nil)
var aircraftModal = storyboard.instantiateViewControllerWithIdentifier("AircraftModal") as! AircraftPickerVC
let pc = aircraftModal.popoverPresentationController
pc?.sourceView = self.view
pc?.delegate = delegate
return aircraftModal
}
}
Note how I have to set the type of controller to LogbookFormTVC in order for its protocol conformity to come in with it. But I want this function to work with any class (that conforms to the right protocol, of course).
Great. So pass an object of the type "conforms to the right protocol:"
func showAircraftPicker(controller: UIPopoverPresentationControllerDelegate) -> UIViewController{
This is exactly what protocols exist to allow you to do.
If you want to conform to multiple restrictions simultaneously, a generic is handy:
func showAircraftPicker<T: UIViewController
where T:UIPopoverPresentationControllerDelegate>(controller: T) -> UIViewController {