In iOS, how to get the name of a UIStoryboard? - ios

If I have reference to a UIStoryboard object, how do I get its name?
For example if I have
class ProfileVC: UIViewController {
override func viewDidLoad() {
let name = self.storyboard?.name // public .name property does not exist
let name = self.storyboard?.value(forKey: "name") // private property access works
}
}
Is there a way to get the name publicly? If not, why did Apple make this private?
Edit: Add use case
The app has multiple storyboards and there is navigation code that is able to navigate to a particular storyboard: navigate(to storyboardName: String). This code always instantiates a new storyboard object and its initial view controller. This is causing bugs in the UI.
I want to do a check to see if the window's root view controller's storyboard is the same as the destination storyboard to prevent re-instantiation.

Related

Problem with transfer of values ​in controllers in Swift

I have login few screens and controllers in my app. First screen is screen with button and moves user to next login view with username, password field and login button. On the controller i have function onClickButton and when i have good data i request to the server with this data.
When server give me callback i have many params about user to set in label in next view.
My structure is like this
Login View -> SecondLogin View and LoginViewController -> TabBarController -> NavigationController -> Table View with TableViewController
My code is
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "afterLoginView" {
if let secondVC = segue.destination as? TabBarViewController {
secondVC.finalName = self.username
}
}
}
When i want transfer my data directly to tableViewController i have error
Thread 1: signal SIGABRT
I do not understand what I'm doing wrong
You'll need these values in almost all view controllers. Create a singleton class to store the logged in user values like this
class UserDetails: NSObject, Codable {
static let shared = UserDetails()
private override init() {
super.init()
}
var finalName: String?
var otherDetails: String?
}
Now when you receive the response from the login api, assign the values in this singleton class.
UserDetails.shared.finalName = "something"//Name received from server callback
Now you can access these values from any view controller.
class TableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
print(UserDetails.shared.finalName)
}
}
You have some work to do to get to the right view controller. Since your segue is only pointing at the UITabBarViewController, you should put in another guard or if/let statement to get you to the UINavigationController, and then another to finally get you to the UITableViewController, where you can actually refer to your finalName variable.
That would look something like:
if let secondVC = segue.destination as? TabBarViewController {
if let navCon = secondVC.viewController[0] as? UINavigationController {
if let tableVC = navCon.topViewController as? nameOfYourTableVC {
tableVC.finalName = self.username
}
The code is untested, just typed off the top of my head, so please proceed with due caution. Issues such as which tab is the correct NavController would also need to be addressed.
You need to use the actual name of your tableView class in that last if/let. A generic UITableViewController will not include your custom variables.
When server give me callback i have many params about user to set in label in next view.
This is a great example of why you should keep the M in MVC. When you get a response back from the server, store the returned data in your data model. (If you don't have a data model, you should make one.) When a view controller gets some data from the user, such as a user name, it should store that in the model. There's little reason to pass raw data back and forth between view controllers directly... just make sure that all your view controllers have a reference to the model, and have them get and set values there as needed.
This kind of approach will make your code a lot more flexible. It allows view controllers to worry about what they need to do their job, and it gets them out of the business of caring what other view controllers need.
My structure is like this
Login View -> SecondLogin View and LoginViewController -> TabBarController -> NavigationController -> Table View with TableViewController
It might make more sense to load the tab bar controller and then present the login view controller(s) modally. The view controllers that are managed by the tab bar controller can all be set up to refuse to do anything useful until the data they need is present in the data model, and that lets the tab bar controller be the root view controller. That will make it easy to set the model for each of it's child view controllers when the app starts up, and the app can then present the modal login view controllers, also set up with references to the model.

How to pass data between Swift files without prepareForSegue?

I currently have three Swift files, one for the main view in a ViewController, and two more which are used for the two views within the first view, which are used for a Segmented Control.
As these don't use segues between each other, I can't use the prepareForSegue method to transfer data between them, so how do transfer the variables and such from one file to another?
This doesn't seem to be a duplicate as other cases such as the one commented are using segues, mine is not.
Are all three Swift classes view controller subclasses?
You have your main view controller with your segmented control. For each segmented, I would create a new view controller subclass.
On your main view controller, for each segment, use a 'Container View' instead of a UIView object.
This will create two new 'screens' in the storyboard, attached to your main view controller with a segue. These new screens will be UIViewControllers, you can change them to be your subclass's as normal.
You can now use your prepareForSegue function as normal to set data in your segmented control view controllers.
So you have something like viewControllerMain, which contains viewSegmentedOne and viewSegmentedTwo, and you want to be able to access `viewControllerMain.myProperty' ?
You can always navigate through the hierarchy to get parent views - but the easiest option could be to include a reference to viewControllerMain in each of the segmented controls
var myParentVC : ViewControllerMain?
then when you create the subviews
mySubView.myParentVC = self
If you are using storyboard for view controllers, then try like this:
let viewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "Your_VC_Identifier");
viewController.Your_var = Your_value_to_assign
NOTE: Define Your_var in your ViewController class
You just need to create an instance of the view controller you want to display, this is easy such as calling on the storyboard instance (usually the presenting view controller has one) instantiateViewController(withIdentifier:), by using an identifier that you provide in the storyboard file.
One created you can pass the data you want by casting it to your view controller class and present it as you prefer.
One method would be using singleton class . https://cocoacasts.com/what-is-a-singleton-and-how-to-create-one-in-swift/ this is how you can make singleton class.
other method could be using nsuserdefaults.
You need to decide which approach is best according to your requirement.
try this:
let defaults = UserDefaults.standard()
defaults.set(yourdata, forKey: "someObject")
print(defaults.object(forKey: "someObject"))
You can try use Extensions for UIViewController
private var storedDataKey: UInt8 = 0
extension UIViewController {
var storedViewControllerData: UIViewController? {
get {
return objc_getAssociatedObject(self, &storedDataKey) as? UIViewController
}
set(newValue) {
objc_setAssociatedObject(self, &storedDataKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_ASSIGN)
}
}
}
This very useful you can send data with chain like:
viewControllerB.storedViewControllerData = viewControllerA.storedViewControllerData
or
func viewDidLoad() {
doSomething(self.storedViewControllerData)
}

How to access a view controller that is not the root view controller from AppDelegate.swift?

I think this is a beginner question. I currently have two view controllers:
class MainViewController: UIViewController {
var markersToDelete = Marker()
func deleteMarkers() {}
}
class MapViewController: UIViewController {
var markersToDelete = Marker()
func deleteMarkers() {}
}
The MapViewController is instantiated with a modal segue in the storyboard. I know that I can access MainViewController in AppDelegate.swift with this code:
var mainViewController = self.window!.rootViewController as! MainViewController
How can I do the same thing with MapViewController?
EDIT
Based on the comments below I am updating this to include more detail. Here is what I am trying to accomplish:
I have IBActions in both view controllers that appends Marker items to the
markersToDelete array. The method deleteMarkers is used to find the corresponding items in the Core Data persistent store and delete them; it is called by AppDelegate when applicationDidEnterBackground or when applicationWillTerminate.
This is the reason why I want to access both view controllers from app delegate. I am starting to think there could be a better way to do this.
Some other answers suggest ways of insantiating the controller, but they seem incorrect since you mentioned the controller is instantiated through a segue. There is no straight forward way of doing what you want, and for a good reason, it should be hard to access things from unrelated code. In your very simple example, this code would work, given the controllers have already been presented:
var mainViewController = self.window!.rootViewController as! MainViewController
let mapViewController = mainViewController.presentedViewController
But I would strongly discourage this type of code inside AppDelegate, it makes a lot of assumptions and will break easily if your navigation structure changes. Be more specific about what you're trying to achieve and you may get answers that lead you to a better architecture.
EDIT
Given the new information provided by the asker, this is a proposed solution to the more specific problem:
I understand you have Marker objects in your Core Data context, and you wish to batch delete some of them. What about adding a bool property to Marker class, and setting it to true for the objects you wish to delete. Then, in AppDelegate, you fetch the markers which have that property set to true, and delete them. This way you don't need to mantain an array, and there is no coupling between classes.
You can access your map controller by calling it's storyboard ID, which you'll need to set in Storyboard. Then just do:
let storyboard = UIStoryboard(name: "Main", bundle: NSBundle.mainBundle())
var mapViewController = storyboard. instantiateViewControllerWithIdentifier("storyboardID") as! MapViewController
You need to set storyboard ID to your mapViewController. Then You can access it from appDelegate.
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main",bundle: nil)
var destViewController : UIViewController
destViewController = mainStoryboard.instantiateViewControllerWithIdentifier("YourViewController") as! UIViewController
You should start by encapsulating your code which is responsible for interaction with CoreData framework API.
Create a singleton class like below and use this class only for insertion/deletion/update operations. This way will ensure loose coupling among your view controllers .
You can get reference to the sharedInstance of DataManager in any class. . Using sharedInstance you can call methods related to your Database operations.
class DataManager {
class var sharedInstance : DataManager {
struct Singleton {
static let instance = DataManager()
}
return Singleton.instance
}
func deleteMarkers() {
//Your logic here
}
}

Present subclassed view controller from another view controller in Swift

I have some problems to use subclasses in Swift, hope someone can help me.
What I have
Two view controllers:
VC1 with just some UIButtons
EffectVC that do some animation depending on the button pressed on VC1
import UIKit
protocol viewAnimation {
func initialStateSet()
func finalStateSet()
}
class EffectVC: UIViewController {
#IBOutlet weak var mainImage: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
self.initialStateSet()
}
override func viewDidAppear(animated: Bool) {
self.finalStateSet()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func initialStateSet() {
}
func finalStateSet() {
}
}
class GrowingEffect : EffectVC, viewAnimation {
override func initialStateSet() {
// some stuff
}
override func finalStateSet() {
// other stuff
}
}
The problem
Maybe a simple question but I can't do what I want in Swift: I need to set a subclass according to the button that is pressed.
In other words I need to present subclassed view controller from my VC1 according to which button is pressed on VC1.
If I press the first button for example I want to show the VC 2 with the class GrowingEffect for use some custom stuff (this stuff must change according to the selected button).
What I tried
use IBAction for create my subclassed VC2 and show it
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let destinationViewController : UIViewController = storyboard.instantiateViewControllerWithIdentifier("EffectVC") as! GrowingEffect
self.presentViewController(destinationViewController, animated: true, completion: nil)
but I got
Could not cast value of type 'ViewAnimationDemo.EffectVC'
(0x109948570) to 'ViewAnimationDemo.GrowingEffect' (0x109948650).
use PrepareForSegue
but I can't set any subclass
What I really want to do
I know there are some other solution, like not using storyboard, but now I describe exactly what I want to do, hoping this is possibile:
have only one view controller in IB (EffectVC) associate with the class EffectVC. The class EffectVC has some subclasses like GrowingEffect.
In my code I want to instantiate the view controller EffectVC with the subclass that I need: for example instantiate the view controller in IB EffectVC with the class GrowingEffect.
I know that if I have one view controller for every subclass of EffectVC I can do what I want but I don't want so many view controller in IB because they are equal, the only things that I want to change are 2 methods.
I think there are some things mixed up in your setup. You should have 2 view controllers, each set up in its file, and each present in the storyboard with its identifier. It is ok if GrowingEffect inherits from EffectVC.
What you currently do with as! GrowingEffect is actually trying to cast the UIViewController instance you get from calling instantiateViewControllerWithIdentifier("EffectVC") to GrowingEffect. This will not work, because it is of type EffectVC.
Rather, you need to call instantiateViewControllerWithIdentifier("EffectVC") if button X is pressed, and instantiateViewControllerWithIdentifier("GrowingEffect") if button Y is pressed.
EDIT
If you use storyboard, you have to instantiate view controllers using instantiateViewControllerWithIdentifier. But you can only get an instance of GrowingEffect, if it is present on the storyboard.
It is not possible to "cast" an instance of EffectVC to GrowingEffect once created.
So, you have two possibilities here:
Use storyboard and put both view controllers on it. Use instantiateViewControllerWithIdentifier to instantiate the view controller you need, depending on the button pressed.
Do not use storyboard. Then you can create the needed view controller manually and use your UINavigationController's pushViewController method to present it.
You can't cast from parent class to child class, parent class just doesn't have the capacity to know what the child is doing. You can however cast from a child to parent, so you would want to set your view controller as GrowingEffect, then cast it to Effect, but again there is no strong suit to doing this either unless some method needs the parent class and you are using the child class. It looks like you need a redesign of how you want your view controllers laid out. Now I am assuming you have 2 children, lets call GrowingEffect and ShrinkingEffect. In your designer, you set your 1 to GrowingEffect and the other to ShrinkingEffect and make sure they have unique identifiers. Then you can use your view to present an Effect, and pass in either of those objects.

Accessing extensionContext from a presented view controller

I've tried creating a custom view controller for a share extension.
A confusing situation happens when I present another view controller on top of the initial view controller that was set on the MainInterface.storyboard. This presented view controller is embedded in a navigation controller (it's the root view controller of it).
I did a check on the presentingViewController
(lldb) po [self presentingViewController]
<_UIViewServiceViewControllerOperator: 0x7a978000>
(lldb) po [[self presentingViewController] extensionContext]
nil
So, the extension context is nil at this point. I could access the extensionContext by passing it around from the presentingViewController to the presentedViewController.
But, I found this behavior is a bit strange. Is the app extension designed to only be accessed from one level of view controller hierarchy?
If you're going to use more than a single view controller in your extension storyboard, you'll have to pass a reference to the extensionContext of the original view controller to the view controller that will ultimately be responsible for completing the extension's request. In the initial view controller:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let destination = segue.destinationViewController as! FinalViewController
destination.originalExtensionContext = self.extensionContext
}
And in your final view controller:
#IBAction func dismissController(sender: UIButton!) {
dismissViewControllerAnimated(true) { () -> Void in
self.originalExtensionContext.completeRequestReturningItems(self.originalExtensionContext.inputItems, completionHandler: nil)
}
Note that you have to create a uniquely named property for the original extension context, since extensionContext already exists as a property name on the superclass UIViewController. You can't pass the existing extensionContext to the UIViewController's property extensionContext as it is a read-only attribute.
The view controller being presented by a view controller should have no problem using the parent's extension. Taking a look at the documentation:
The view controller can check this property to see if it participates in an extension request. If no extension context is set for the current view controller, the system walks up the view controller hierarchy to find a parent view controller that has a non nil extensionContext value.
Therefore, if you can be certain of the fact that your root view controller does indeed have an extensionContext, any view controller presented by this view controller should have access to it, simply through it's own extensionContext property.
Note: If this is not the behaviour you a re observing, this may be a bug with the SDK, and I would recommend filing a radar.
While it's not the best approach for clean code and architecture, it's quite handy:
In root extension controller where extensionContext exists:
final class ShareRootViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
NSExtensionContext.shared = self.extensionContext
}
}
extension NSExtensionContext {
fileprivate(set) static var shared: NSExtensionContext!
}
In any other view controller:
let context = NSExtensionContext.shared

Resources