What is the iOS equivalent of Android's startActivityForResult/setResult? - ios

I am an Android programmer learning iOS. I have a swift view1 that calls an objective C view2. I am looking to return a String from view2 back to view 1.
In Android, we would simply view1.startActivityForResult(View2.class)
What is the iOS way to do this?

ViewController2.h - Objective-C
#protocol ViewController2Delegate <NSObject>
- (void)sendStringBack:(NSString *)aString;
#end
#interface ViewController2 : UIViewController
#property (nonatomic, weak) id<ViewController2Delegate> delegate;
#end
ViewController2.m - Objective-C
// When you want to send the string back and dismiss the view:
[self.delegate sendStringBack:theStringToSendBack];
ViewController1.swift - Swift
#objc class ViewController1: UIViewController, ViewController2Delegate {
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let viewContr = segue.destinationViewController as! ViewController2
viewContr.delegate = self
}
func sendStringBack(aString: String) {
let aVariable = aString
// do something with the string
// dismiss the view if you presented it modally
self.dismissViewController(self, animated: true, completion: nil)
// OR
// dismiss the view if you presented it with show/push
self.navigationController?.popViewControllerAnimated(true)
}
}

There is nothing like startActvityForResult() in iOS. You have to do it manually. I did it with the following process.
ViewControllerOne has a form that needed data from viewControllerTwo. ViewControllerTwo has a tableView with dataList. By tapping a data on the tableView of ViewControllerTwo it will go back to ViewControllerOne with the table data that was tapped.
Create a global variable for storing data.
var data:String = ""
Open ViewControllerTwo from ViewControllerOne with segue.
In ViewControllerTwo which has a tablewView. In didSelectRowAt method set the data to global variable and Call the method
self.navigationController?.popViewController(animated: true) like below.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
data = dataList[indexPath.row]
self.navigationController?.popViewController(animated: true)
}
It will go back to ViewControllerOne. Now You have catch the data in viewWillAppear() or viewDidAppear() method. For example:
override func viewWillAppear(_ animated: Bool) {
if data != ""{
// Do whatever you want with this data. after doing your work set the data value to empty.
data = ""
}
}
Thats it. Happy Coding. It will work 100% sure

Use UIViewController+CallBack for this purpose.
Only need to set the resulting object from parent controller and child controller will override the protocol method and return any kind of object to the parent.
(id)viewUnloadWithResultObject
No ned to set the protocol
UIViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:#"NewViewController"];
[controller setResultBlock:^(id resultObject) {
NSLog(#"New View Controller did pop and resulting object is: %#", resultObject);
}];
[self.navigationController pushViewController:controller animated:YES];
You can also present the view controller and get same resulting object
UIViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:#"NewViewController"];
[controller setResultBlock:^(id resultObject) {
NSLog(#"New View Controller did dismissed and resulting object is: %#", resultObject);
}];
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:controller];
[self.navigationController presentViewController:navController animated:YES completion:NULL];
Also you can have view unload call back methods. i.e.
(void)viewWillUnloadCallBack
(void)viewDidUnloadCallBack
Just inherit protocol and override these methods in your view controller to get the unload call backs.

Related

Navigate from NavigationController to another NavigationController with a back button

Hello,
so i have this storyboard structure i have a button in ViewController B i need that button to point to the bottom NavigationController in which it has the ViewController C as rootViewController so when i press a button in ViewContoller B it takes me to C now i need a back button on C to take me back to B
i have tried looking but all of the reference materials show how to navigate between ViewController not NavigationControllers
I think you cannot push Navigation Controller inside another uinavigation controller but alternativaly you can present another nav controller.
Hope it works for you.
I have created simple demo like this.
class ViewController: UIViewController {
#IBOutlet weak var btnCustom: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func btnForward(_ sender: Any) {
/*Uncomment the code if you want to present programatically*/
/*if let navi2 = self.storyboard?.instantiateViewController(withIdentifier: "nav2"){
self.present(navi2, animated: true) {
}
}*/
}
}
class ViewController2: UIViewController {
#IBOutlet weak var btnCustom: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func btnBack(_ sender: Any) {
if let parentNav = self.navigationController {
parentNav.dismiss(animated: true) {
}
}
}}
You can use a single navigation controller, but overwrite the back buttons action.
By default it will pop the current view controller from the navigation stack and go to the previous one.
On the back action in VC B just push to the VC C, you can do this either in storyBoard by drag-drop from the back button to VC C or by code by writing:
self.navigationControlller.push(VC C)
If using 2 navigationControllers is a requirement that cannot be avoided, you can solve the problem statement in the following way.
In ViewControllerC on a UIButton's tap event, get the instance of ViewControllerB in the parent navigationController and pop to it like so,
#objc func backAction() {
let parentNav = self.navigationController?.navigationController
if let vcB = parentNav?.viewControllers.first(where: { $0 is ViewControllerB }) {
parentNav?.popToViewController(vcB, animated: true)
}
}
Add the backAction() to backButton's target like,
backButton.addTarget(self, action: #selector(backAction), for: .touchUpInside)
Put this code inside your C's viewController back button action method. Also you have to give identifiers names to your views in the storyboard screen.
...
//It possible to change between different storyboards, but in your case all the views share the same storyboard.
UIStoryboard* storyboardOfA_B = [UIStoryboard storyboardWithName: #"storyboardOfA_B" bundle:nil];
UINavigationController* navigationControllerOfA_B = [storyboardOfA_B instantiateViewControllerWithIdentifier:#"navigationControllerOfA_B"];
UIViewController* viewControllerB = [storyboardOfA_B instantiateViewControllerWithIdentifier:#"viewControllerB"];
//I'm supposing that you have the action method of the back button in the C's view controller so I use self.
UINavigationController* navigationControllerOfC = self.navigationController;
[navigationControllerOfC pushViewController: navigationControllerOfA_B animated:NO];
[navigationControllerOfA_B pushViewController: viewControllerB animated:NO];
...
Maybe this is not the best way but is a way

Initial func in 1st view from 2nd view (swift)

I have a button on 2nd viewController, after pressing that button, I would like to dismiss the 2nd viewController and go back to the 1st view controller and immediately call a function that coded inside 1st ViewController swift file.
May I know how can I do that? By segue?
There are many way to do this one of the best way is using protocol and delegate.
You can create one protocol and extend that protocol in your ViewController1. Now create the delegate of protocol in ViewController2 and pass reference of that delegate in the ViewController1's prepareForSegue method.
First create one protocol like this
protocol PassdataDelegate {
func passData()
}
Now extend this protocol in ViewController1 like this and pass the reference of delegate in prepareForSegue method
class ViewController1 : UIViewController, PassdataDelegate {
func passData() {
//Here call your function
self.callMyFunction()
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (segue.identifier == "SegueIdentifier") {
let destVC = segue.destinationViewController as! ViewController2
destVC.delegate = self
}
}
}
Now create the delegate object of protocolin ViewController2 like this
class ViewController2 : UIViewController {
var delegate: PassdataDelegate?
//Now call the method of this delegate in Button action
#IBAction func buttonClick(sender: UIButton) {
self.delegate.passData()
//Now dismiss the controller
}
}
Note: - Here i am passing stringbut you can pass any type of object that you have declare in your delegate method.
You can refer unwind segue.
class ViewController1 {
#IBAction func doSomeStuffAfterReload(segue: UIStoryboardSegue) {
// do whatever you need to do here.
}
}
On storyboard, from ViewController2 Ctrl+Drag from the button to the exit outlet and select doSomeStuffAfterReload.
You can see it in action here: https://spin.atomicobject.com/2014/10/25/ios-unwind-segues/
Happy coding^^

How get visible / current viewcontroller in non-viewcontroller class? Swift

I have information box object - "Info view". And I wanted to append this view to current VC view, sometimes directly in VC class sometimes outside VC class, ex. in my framework.
I don't want always pass current VC in method argument, like
class InfoView: UIView {
/* Initialization methods */
func show(viewController: ViewController) {
/* some code */
viewController.addSubview(self)
}
}
I want get current VC directly in my "Info view" class:
class InfoView: UIView {
/* Initialization methods */
func show() {
let viewController = /* need get current VC */
/* some code */
viewController.addSubview(self)
}
}
rootViewController property in UIApplication.sharedApplication() returns current VC, but after transitions this VC not changed.
How I can get need get current VC?
rootViewController is a good way to go. Your root VC is probably a navigation controller, so you could use something like this:
func currentVC() -> UIViewController? {
guard let navigationController = UIApplication.sharedApplication().keyWindow?.rootViewController as? UINavigationController else { return nil }
return navigationController.viewControllers.last
}
Hi Artem and DeyaEldeen,
If I am not wrong you want to access view controller on storyboard.
You can get instance of any view controller
Steps
1 set storyboard id for view controller in its Custom Class Attribute.
2 In your info view class
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
MyViewcontroller *c = [storyboard instantiateViewControllerWithIdentifier:#"MyViewcontrollerStoryboardID"]
I hope it help you

Reload and not reload if press back from different view controllers. Swift

The top three answers can solve my questions. It is hard to pick which one is the best. So, I just pick the one who is the first to answer my question. Sorry for amateur and iOSEnthusiatic. Thank you for your help. I appreciate it.
ViewController 1 has a table view.
My question is how to reload the table view only if I click back from view controller 2, and not reload the table view if I click back from view controller 3.
Right now, my code for back button is
#IBAction func backButtonTapped(sender: AnyObject) {
self.dismissViewControllerAnimated(true, completion: nil)
}
In view controller 1. I know that the table view would be reloaded from either view controller 2 or 3
override func viewDidAppear(animated: Bool) {
loadTable()
}
I tried to put loadTable() in viewDidLoad and try to write the below code for back button in view controller 2. But, it doesn't work.
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateViewControllerWithIdentifier("UserHomePageViewController") as! UserHomePageViewController
controller.viewDidLoad()
Any suggestion what I should do? Thank you for your help.
EDIT:
I think this is an easier way to do it, but it still does not work as I thought. I guess it is because the viewDidAppear is executed before the call of reloadTableBool. Correct? Is there any way to fix it? Thank you. You help would be appreciated.
class 2ViewController
#IBAction func backButtonTapped(sender: AnyObject) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateViewControllerWithIdentifier("1ViewController") as! 1ViewController
print("viewcontroller2 before call: \(controller.reloadTableBool)")
controller.reloadTableBool = false
print("viewcontroller2 after call: \(controller.reloadTableBool)")
self.dismissViewControllerAnimated(true, completion: nil)
}
class 1ViewController
var reloadTableBool = true
override func viewDidAppear(animated: Bool) {
print("viewcontroller1: \(reloadTableBool)")
if reloadTableBool == true {
loadTable()
}
}
When I click back on view controller 2, it prints
viewcontroller2 before call: true
viewcontroller2 after call: false
viewcontroller1: true
Here is a link to a question I answered a couple days ago. Use the navigation controller delegate to handle the back button. In your second view controller, set the delegate to self and reload the tableview when you press the back button.
override func viewDidLoad() {
super.viewDidLoad()
navigationController?.delegate = self
}
func navigationController(navigationController: UINavigationController, willShowViewController viewController: UIViewController, animated: Bool) {
if let controller = viewController as? FirstViewController {
controller.tableView.reloadData()
}
}
NOTE:
I'm assuming you're using the back button of the navigation controller here.
EDIT: Another example using your manually added back button:
#IBAction func backButtonTapped(sender: AnyObject) {
if let viewControllers = app.window?.rootViewController?.childViewControllers {
viewControllers.forEach { ($0 as? FirstViewController)?.tableView.reloadData() }
}
self.dismissViewControllerAnimated(true, completion: nil)
}
Seeing as you are using a navigation controller:
#IBAction func backButtonTapped(sender: AnyObject) {
navigationController?.viewControllers.forEach { ($0 as? FirstViewController)?.tableView.reloadData() }
self.dismissViewControllerAnimated(true, completion: nil)
}
If displaying vc2 is performed by vc1 and is always sure to invalidate the data in vc1, you could do the following:
add a needsReload boolean instance variable to vc1
set it to true whenever you display vc2 (and when instanciating vc1 eg in awakeFromNib if coming from a storyboard)
only perform the content of loadTable if needsReload is true (maybe refactor this logic into a loadTableIfNeeded)
don't forget to set needsReload to false in the end of loadTableIfNeeded
This invalidation pattern is found throughout UIKit, see for example UIView setNeedsLayout/layoutIfNeeded. The advantage is that even if several events cause the data to invalidate, it will only actually get refreshed when you need it.
In your situation it has the additional advantage of keeping the logic contained in vc1 and not creating unnecessary coupling between your VCs, which is always good.
---UPDATE: sample implementation (ObjC but you'll get the idea)
You only need to handle this in VC1, forget about all the back button stuff in VC2. This implementation will mark VC1 for reload as soon as VC2 is presented, but will actually reload only on viewWillAppear, when VC2 is dismissed.
---UPDATE 2: Added a conditional reload based on a delegate callback
Note that _needsReload is now set in the delegate callback, not when VC2 is first presented. Instead we set VC1 as the delegate of VC2. (_needsReload logic is actually unnecessary using this method, kept it for reference)
//VC2: add a delegate to the interface
#class VC2;
#protocol VC2Delegate
- (void) viewController:(VC2*)myVC2 didFinishEditingWithChanges:(BOOL)hasChanges;
#end
#interface VC2
#property (nonatomic, weak) id<VC2Delegate> delegate
#end
#implementation VC2
- (IBAction) finishWithChanges
{
[self.delegate viewController:self didFinishEditingWithChanges:YES];
}
- (IBAction) finishWithoutChanges
{
[self.delegate viewController:self didFinishEditingWithChanges:NO];
}
#end
//VC1: implement the VC2Delegate protocol
#interface VC1 () <VC2Delegate>
#end
#implementation VC1
{
BOOL _needsReload
}
- (void) awakeFromNib
{
//adding this for completeness but the way you did it in Swift (at init) is correct
[super awakeFromNib];
_needsReload = YES;
}
- (void) viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self reloadTableIfNeeded];
}
- (IBAction) displayVC2
{
VC2* myVC2 = ... //instanciate your VC2 here
myVC2.delegate = self; //set as the delegate of VC2
[self presentViewController:myVC2 animated:YES completion:nil];
}
- (void) viewController:(VC2*)myVC2 didFinishEditingWithChanges:(BOOL)hasChanges
{
_needsReload = hasChanges;
[self reloadTableIfNeeded];
}
- (void) reloadTableIfNeeded
{
if (_needsReload) {
[self.tableView reloadData];
_needsReload = NO;
}
}
#end
You can use notification approach easily for this.
Add observer in your 1st ViewController in viewDidLoad method.
NSNotificationCenter.defaultCenter().addObserver(self, selector: "reloadTable:", name: "reloadTable", object: nil)
func reloadTable(notification : NSNotification){
let isReload : NSNumber = notification.userInfo!["isReload"] as! NSNumber
if (isReload.boolValue) {
self.tableView.reloadData()
}
}
Then post notification like this from your 2nd and 3rd ViewController respectively when you call dismissViewController.
// From 2nd viewcontroller
NSNotificationCenter.defaultCenter().postNotificationName("reloadTable", object: nil, userInfo: ["isReload" : NSNumber(bool: false)])
// From 3rd viewcontroller
NSNotificationCenter.defaultCenter().postNotificationName("reloadTable", object: nil, userInfo: ["isReload" : NSNumber(bool: true)])

pop a viewController without release

People, i want to pop a viewController using the normal back button on NavigationView controller without release the customized view that was created by the user, any one know some way to do that?
Because the natural flow of the navigation controller is release the "poped" viewController! Thanks for the help!
You need to retain a copy of the view controller elsewhere. Perhaps inside the class containing the navigation controller. Then push this back onto the stack when required.
Additionally check out UINavigationControllerDelegate
can you save the datas in the "popped" view controller? when it comes out again, populate it? When view controller is popped, it should be released.
You can navigation.viewControllers array copy in to global array before pop the custom view. After pop view global navigation.viewControllers assign global array.
NSArray create in AppDelegate
appDelegate.nav = self.navigationController.viewControllers;
[self.navigationController popViewControllerAnimated:YES];
then after assign global array in poped view
-(void)viewWillAppear:(BOOL)animated
{
self.navigationController.viewControllers = appDelegate.nav;
}
Well people tha answer is, make your controller from the StoryBoard, and don't use segue to call.
if(comparacao == nil)
{
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main_iPhone" bundle: nil];
comparacao = [storyboard instantiateViewControllerWithIdentifier:#"ComparacaoView"];
}
[self.navigationController pushViewController:comparacao animated:YES];
So with this, I every use the before instance created, and every thing that my user do in this view was maintained.
Swift Code:
#IBAction func pushButtonAction(_ sender: UIButton) {
if let exitingSecViewCon = secondViewController {
navigationController?.pushViewController(exitingSecViewCon, animated: true)
}else{
self.performSegue(withIdentifier: "second", sender: self)
}
}
var secondViewController: SecondViewController? //Keep Strong Pointer
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "second" {
if let sec = segue.destination as? SecondViewController {
sec.name = "ABC"
secondViewController = sec;
}
}
}
Console Log:(Same address for SecondViewController)
ADD=<RetainCount.SecondViewController: 0x7fb5ac630cf0>
ADD=<RetainCount.SecondViewController: 0x7fb5ac630cf0>
ADD=<RetainCount.SecondViewController: 0x7fb5ac630cf0>

Resources