Button action is not working on subView - ios

In one of my iOS project, I have added a subview for Filter ViewController(subview) on my Feed ViewController(main view) programmatically.
There are few button on Filter ViewController to select price, city etc.
The outlets are connected but when I am trying to shoot button action, it is not working.
I have also enabled the isUserInteractionEnabled but still it is not working.
Acc. to me, this is something related to subview on a view !! but to resolve this. Can you suggest me how to shoot a button action of subview it happen ?
class FilterViewController: BaseUIViewController{
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
cityButton.isUserInteractionEnabled = true
}
#IBAction func selectCity(_ sender: Any)
{
print("selectCity action")
}
}

Add your FilterViewController as subview to your ViewController using the code below
filterViewController.willMove(toParentViewController: self)
self.view.addSubview(filterViewController.view)
self.addChildViewController(filterViewController)
filterViewController.didMove(toParentViewController: self)

#SandeepBhandari solution is still valid for similar issues.
These UIViewController extensions will be useful.
extension UIViewController {
func add(asChildViewController viewController: UIViewController, containerView: UIView) {
addChild(viewController)
containerView.addSubview(viewController.view)
viewController.view.frame = containerView.bounds
viewController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
viewController.didMove(toParent: self)
}
func remove(asChildViewController viewController: UIViewController) {
viewController.willMove(toParent: nil)
viewController.view.removeFromSuperview()
viewController.removeFromParent()
}
}

Related

Finish editing UITextField on back button tap

I have 2 controllers inside NavigationController. First pushes the second one to the stack and user can interact with the text field there. Then (in one scenario) user will tap on back button to be taken to the previous screen. Assuming that loading of second one is 'heavy', so I will be keeping only one instance of it once it is needed.
Expected:
I would like to have keyboard hidden once back button is pressed.
Actual:
First responder keeps being restored when I go back to the second for the second time. How to prevent that? Resigning first responder also doesn't do the trick there...
Problem demo:
https://gitlab.com/matrejek/TestApp
Major code parts:
class FirstViewController: UIViewController {
var child: UIViewController = {
let storyboard = UIStoryboard(name: "Main", bundle: Bundle.main)
let vc = storyboard.instantiateViewController(withIdentifier: "child")
return vc
}()
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func onButtonTap(_ sender: Any) {
self.navigationController?.pushViewController(child, animated: true)
}
}
class SecondViewController: UIViewController {
#IBOutlet weak var textField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
view.endEditing(true)
}
}
This does seem odd --- and it seems like your approach should work.
Apparently (based on quick testing), since you are not allowing the Navigation Controller to release the SecondVC, the text field is remaining "active."
If you add this to SecondViewController, it will prevent the keyboard from "auto re-showing" the next time you navigate to the controller - not sure it will be suitable for you, but it will do the job:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
DispatchQueue.main.async {
self.view.endEditing(true)
}
}
Edit: Jan 25 2020
Based on new comments, yes, this seems to be a bug.
My previous work-around answer worked -- sort of. The result was the keyboard popping up and then disappearing on subsequent pushes of child.
Following is a better work-around. We have SecondViewController conform to UITextFieldDelegate and add a BOOL class var / property that will prevent the text field from becoming first responder. Comments should be clear:
class FirstViewController: UIViewController {
var child: UIViewController = {
let storyboard = UIStoryboard(name: "Main", bundle: Bundle.main)
let vc = storyboard.instantiateViewController(withIdentifier: "child")
return vc
}()
#IBAction func onButtonTap(_ sender: Any) {
self.navigationController?.pushViewController(child, animated: true)
}
}
// conform to UITextFieldDelegate
class SecondViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var textField: UITextField!
// bool var to prevent text field re-becoming first responder
// when VC is pushed a second time
var bResist: Bool = false
override func viewDidLoad() {
super.viewDidLoad()
// assign text field delegate
textField.delegate = self
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// view has appeared, so allow text field to become first responder
bResist = false
}
func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
return !bResist
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// end editing on this view
view.endEditing(true)
// we want to resist becoming first responder on next push
bResist = true
}
}

How to call a function in the first controller after dismissing the second controller

I have two UIViewController, when I click a button, it goes from the first view controller to the second one. And before that, I animated a UIView to move to another place. After dismissing the second View Controller, I want to move the UIView in the first view controller back to where it originally was. However, when I call a function from the second View Controller to animate the UIview in the first view controller after dismissing the second one, It could not get the UIView's properties, and cannot do anything with it. I think because the first UIViewController is not loaded yet. Is that the problem? And How should I solve this?
There are two solutions you can either use swift closures
class FirstViewController: UIViewController {
#IBAction func start(_ sender: Any) {
guard let secondController = self.storyboard?.instantiateViewController(withIdentifier: "SecondController") as? SecondController else { return }
secondController.callbackClosure = { [weak self] in
print("Do your stuff")
}
self.navigationController?.pushViewController(secondController, animated: true)
}
}
//----------------------------
class SecondController: UIViewController {
var callbackClosure: ((Void) -> Void)?
override func viewWillDisappear(_ animated: Bool) {
callbackClosure?()
}
}
or you can use protocols
class FirstViewController: UIViewController {
#IBAction func start(_ sender: Any) {
guard let secondController = self.storyboard?.instantiateViewController(withIdentifier: "SecondController") as? SecondController else { return }
secondController.delegate = self
self.navigationController?.pushViewController(secondController, animated: true)
}
}
extension ViewController : ViewControllerSecDelegate {
func didBackButtonPressed(){
print("Do your stuff")
}
}
//--------------------------
protocol SecondControllerDelegate : NSObjectProtocol {
func didBackButtonPressed()
}
class SecondController: UIViewController {
weak var delegate: SecondControllerDelegate?
override func viewWillDisappear(_ animated: Bool) {
delegate?.didBackButtonPressed()
}
}
You can try to use a closure. Something like this:
class FirstViewController: UIViewController {
#IBOutlet weak var nextControllerButton: UIButton!
private let animatableView: UIView = UIView()
private func methodsForSomeAnimation() {
/*
perform some animation with 'animatableView'
*/
}
#IBAction func nextControllerButtonAction() {
// you can choose any other way to initialize controller :)
let storyboard = UIStoryboard(name: "Main", bundle: nil)
guard let secondController = storyboard.instantiateViewController(withIdentifier: "SecondViewController") as? SecondViewController else { return }
secondController.callbackClosure = { [weak self] in
self?.methodsForSomeAnimation()
}
present(secondController, animated: true, completion: nil)
}
}
class SecondViewController: UIViewController {
#IBOutlet weak var dismissButton: UIButton!
var callbackClosure: ((Void) -> Void)?
#IBAction func dismissButtonAction() {
callbackClosure?()
dismiss(animated: true, completion: nil)
/*
or you call 'callbackClosure' in dismiss completion
dismiss(animated: true) { [weak self] in
self?.callbackClosure?()
}
*/
}
}
When you present your second view controller you can pass an instance of the first view controller.
The second VC could hold an instance of the first VC like such:
weak var firstViewController: NameOfController?
then when your presenting the second VC make sure you set the value so it's not nil like so:
firstViewController = self
After you've done this you'll be able to access that viewControllers functions.
iOS 11.x Swift 4.0
In calling VC you put this code ...
private struct Constants {
static let ScannerViewController = "Scan VC"
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == Constants.ScannerViewController {
let svc = destination as? ScannerViewController
svc?.firstViewController = self
}
}
Where you have named the segue in my case "Scan VC", this is what it looks like in Xcode panel.
Now in scan VC we got this just under the class declaration
weak var firstViewController: HiddingViewController?
Now later in your code, when your ready to return I simply set my concerned variables in my firstViewController like this ...
self.firstViewController?.globalUUID = code
Which I have setup in the HiddingViewController like this ...
var globalUUID: String? {
didSet {
startScanning()
}
}
So basically when I close the scanning VC I set the variable globalUUID which in term starts the scanning method here.
When you are saying it could not get the UIView's properties it's because you put it as private ? Why you don't replace your UIView in the first controller when it disappears before to go to your secondViewController. I think it's a case where you have to clean up your view controller state before to go further to your second view controller.
Check IOS lifecycle methods : viewWillDisappear or viewDidDisappear through Apple documentation and just do your animation in one of these methods.
Very simple solution actually... Just put your animation in the viewDidAppear method. This method is called every time the view loads.
class firstViewController: UIViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// insert animation here to run when FirstViewController appears...
}
}

How to center the popupview that contain dynamic height content ??

I am now implementation the pop up dialog for my iOS app according to this youtube video "https://www.youtube.com/watch?v=FgCIRMz_3dE".But the problem is I can't set my pop up view to fixed height because there is dynamic height label in my popup view which is inside my popup view controller.Can anyone tell me how to solve this solution?Thanks for your attention.
Here is my code to open popup view controller from parent view controller.
let PopUpVC = UIStoryboard(name:"Main", bundle:nil).instantiateViewController(withIdentifier: "FeedPopUpViewController") as! FeedPopUpViewController
self.addChildViewController(PopUpVC)
PopUpVC.view.frame = self.view.frame
self.view.addSubview(PopUpVC.view)
PopUpVC.didMove(toParentViewController: self)
Here is my code for FeedPopUpView Controller
import UIKit
class FeedPopUpViewController: UIViewController {
#IBOutlet weak var action_Label: UILabel!
#IBAction func dismiss(_ sender: Any) {
print("pop up is dismissed")
self.view.removeFromSuperview()
}
override func viewDidLoad() {
self.showAnimate()
super.viewDidLoad()
print("pop up is created")
}
override func viewWillAppear(_ animated: Bool) {
print("pop up is appeared")
}
func showAnimate(){
}
}
PopUpVC.view.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
This should do the work :)

Why is the top layout guide moving in my iMessage extension

I have an iMessage extension and I'm having some issues with the top layout guide. I have an MSMessagesAppViewController that handles changes between presentation styles. In my extension I have a button. When it is clicked I transition to expanded presentation style and then present a view controller modally. Here's the problem: my UI in the second VC is getting hidden behind the top navigation bar. I thought this was strange as I set my constraints to the top layout guide. So I dug through my code and started debugging the top layout guide. I noticed that after I transition to expanded presentation style, topLayoutGuide.length = 86. That's how it should be. But when I present the second view controller modally, the top layout guide is reset to 0. Why isn't it 86 as it should be? Here is my code:
In my main viewController:
#IBAction func addStickerButtonPressed(_ sender: AnyObject) {
shouldPerformCreateSegue = true
theSender = sender
requestPresentationStyle(.expanded)
}
override func didTransition(to presentationStyle: MSMessagesAppPresentationStyle) {
if presentationStyle == .expanded {
if shouldPerformCreateSegue == true {
shouldPerformCreateSegue = false
performSegue(withIdentifier: "CreateStickerSegue", sender: theSender)//here is where I present the new viewController
} else {
searchBar.becomeFirstResponder()
searchBar.placeholder = nil
searchBar.showsCancelButton = true
searchBar.tintColor = UIColor.white
}
} else {
searchBar.showsCancelButton = false
}
print(topLayoutGuide.length) //This prints out 86
}
In the other modally presented view controller:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.view.addConstraint(navBar.topAnchor.constraint(equalTo: topLayoutGuide.bottomAnchor))
print(topLayoutGuide.length) //This prints out 0
}
As a workaround I use UIPresentationController, which shifts the modal view controller by topLayoutGuide.length points:
class MyViewController: MSMessagesAppViewController {
private func presentModalViewController() {
let imagePicker = UIImagePickerController()
imagePicker.delegate = self
imagePicker.sourceType = .savedPhotosAlbum
imagePicker.modalPresentationStyle = .custom
imagePicker.transitioningDelegate = self
present(imagePicker, animated: true, completion: nil)
}
}
// MARK: - UIViewControllerTransitioningDelegate
extension MyViewController: UIViewControllerTransitioningDelegate {
func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
let vc = PresentationController(presentedViewController: presented, presenting: presenting)
// I really don't want to hardcode the value of topLayoutGuideLength here, but when the extension is in compact mode, topLayoutGuide.length returns 172.0.
vc.topLayoutGuideLength = topLayoutGuide.length > 100 ? 86.0 : topLayoutGuide.length
return vc
}
}
class PresentationController: UIPresentationController {
var topLayoutGuideLength: CGFloat = 0.0
override var frameOfPresentedViewInContainerView: CGRect {
guard let containerView = containerView else {
return super.frameOfPresentedViewInContainerView
}
return CGRect(x: 0, y: topLayoutGuideLength, width: containerView.bounds.width, height: containerView.bounds.height - topLayoutGuideLength)
}
}
The only problem is when you're calling presentModalViewController from compact mode, topLayoutGuide.length is 172.0 for unknown reason. So I had to hardcode a value for that case.
I believe this was known bug on previous iOS 10 beta. I had same issue and top and bottom layout guide works as I expect after I upgraded iOS version to latest.
I used a slightly varied version of Andrey's
class MyViewController: MSMessagesAppViewController {
private func presentModalViewController() {
let imagePicker = UIImagePickerController()
imagePicker.delegate = self
imagePicker.sourceType = .savedPhotosAlbum
imagePicker.modalPresentationStyle = .custom
imagePicker.transitioningDelegate = self
present(
imagePicker,
animated: true,
completion: nil
)
}
}
extension MyViewController: UIViewControllerTransitioningDelegate {
func presentationController(
forPresented presented: UIViewController,
presenting: UIViewController?,
source: UIViewController
) -> UIPresentationController? {
let vc = PresentationController(
presentedViewController: presented,
presenting: presenting
)
vc.framePresented = modalBoundaries.frame
return vc
}
}
class PresentationController: UIPresentationController {
var framePresented = CGRect.zero
override var frameOfPresentedViewInContainerView: CGRect {
return framePresented
}
}
modalBoundaries being a dummy UIView constrained (via XIB in my case) to respect any TopLayoutGuide length.

How to perform segue from inside UIView

How can a prepare a segue properly from inside a UIView NOT UIViewController
My UIViewController has a container view and inside that container view has a button.
class myInnerView: UIView {
...
func myButton(gesture: UIGestureRecognizer){
//calling a perform segue from another UIViewController does not recognize the SegueID
//ViewController().self.showProfile(self.id) -- DOES NOT WORK
}
}
class ViewController: UIViewController {
...
func showProfile(id: String){
...
self.performSegueWithIdentifier("toViewProfile", sender: self)
}
}
Your view should not handle a button tap. That should be handled by the controller. (This is why it is called "controller", while the UIView is called "view").
MVC as described by Apple.
If the button is a subview of your view controller's view, you should be able to drag an IBAction onTouchUpInside from the button to your view controller. You can then initiate the segue from that method.
One of the solution is to add UITapGestureRecognizer to your button but from inside the UIViewController :
class ViewController: UIViewController {
var myInnerView = myInnerView()
override func viewDidLoad() {
let tap = UITapGestureRecognizer(target: self, action: #selector(ViewController.handleTapGesture))
self.myInnerView.myButton.addGestureRecognizer(tap)
}
#objc func handleTapGesture(){
performSegue(withIdentifier: "toViewProfile", sender: nil)
}
}
class myInnerView: UIView {
// make outlet of your button so you can add the tapGestureRecognizer from your ViewController and thats all
#IBOutlet public weak var myButton: UIButton!
}
You need to tap gesture for view to navigate
let customView = myInnerView()
let gestureRec = UITapGestureRecognizer(target: self, action: #selector (self.someAction (_:)))
myView.addGestureRecognizer(customView)
func someAction(_ sender:UITapGestureRecognizer){
let controller = storyboard?.instantiateViewController(withIdentifier: "someViewController")
self.present(controller!, animated: true, completion: nil)
// swift 2
// self.presentViewController(controller, animated: true, completion: nil)
}

Resources