I have a parent view controller that acts as a container for a UIPageViewControllerand another UIViewController (called commentsViewController). I want to show the inputAccessoryView inside the commentsViewController however it doesn't seem to be working. I have added the commentsViewController to the parent like so:
commentsViewController = storyboard!.instantiateViewControllerWithIdentifier("CommentsViewController")! as! CommentsViewController
self.addChildViewController(commentsViewController)
self.view.addSubview(commentsViewController.view)
//...I have set some autolayout constraints here
commentsViewController.didMoveToParentViewController(self)
Then inside CommentsViewController, I have the following:
#IBOutlet var customView: UIView!
override var inputAccessoryView: UIView {
return customView
}
override func canBecomeFirstResponder() -> Bool {
return true
}
override func viewDidLoad() {
super.viewDidLoad()
self.becomeFirstResponder()
}
Unfortunately this doesn't work and the inputAccessoryView is not displayed... Can anyone see what I'm missing?
Thank you.
Turns out that because I was animating the view, becomeFirstResponder() automatically returned false. After the animation it works fine.
Related
I have a UIView subclass (a graph) embedded inside of a UIScrollview inside of a UIViewController which is one of a UIPageViewControllers pages... I'm trying to disable scrolling of the UIPageViewController so that the user scan scroll to see the far left of the graph without paging back. How can I do this? Is a ScrollView even the correct tool for this job?
class HistoricalHealthDataViewController: UIViewController, UIScrollViewDelegate {
#IBOutlet weak var graphScrollView: UIScrollView!
#IBOutlet weak var hockeyTrackerGraphView: HockeyTrackerGraphView! {
didSet {
self.hockeyTrackerGraphView.graphableObjects = HFRGraphableObjects
}
}
var HFRGraphableObjects: [HockeyTrackerGraphableObject] = []
override func viewDidLoad() {
super.viewDidLoad()
graphScrollView.delegate = self
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if let parentPageViewController = parent as? HistoricalPageViewController {
for gestureRecognizer in parentPageViewController.gestureRecognizers {
print("gestureRecognizer")
gestureRecognizer.isEnabled = false
}
}
}
}
You don't show the code for it, but I'm assuming you implement UIPageViewControllerDataSource protocol and somewhere you have the methods:
func pageViewController(UIPageViewController, viewControllerBefore: UIViewController) -> UIViewController? {
}
func pageViewController(UIPageViewController, viewControllerAfter: UIViewController) -> UIViewController? {
}
If you arrange for those methods to return nil while paging is disabled, the page view won't scroll to a different page.
I implemented docked inputAccessoryView like this:
lazy var inputView: UIView = {
// custom creation
}()
override var inputAccessoryView: UIView? {
return inputView
}
override var canBecomeFirstResponder: Bool {
return true
}
It works almost great, the issue is that inputView hides after I dismiss UIViewController (it is visible for 1 second, and then disappears).
I have a viewController with a UISegmentedControl and a UIButton.
Within this viewController, I have two containers, each containing one viewController with a UITextField inside.
I want to save the values in the textField on the click of the button.
Here's the code I have written so far:
View Controller:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
//
//
containerA.showView()
containerB.hideView()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func buttonTapped(sender: UIButton) {
print(ContainerAViewController.sharedInstance.textFieldA)
}
#IBAction func segmentedControlValueChanged(sender: AnyObject) {
switch(sender.selectedSegmentIndex) {
case 0 : containerA.showView()
containerB.hideView()
case 1 : containerB.showView()
containerA.hideView()
default : containerA.showView()
containerB.hideView()
}
}
#IBOutlet weak var containerA: UIView!
#IBOutlet weak var containerB: UIView!
func hideView(view: UIView) {
view.userInteractionEnabled = false
view.hidden = true
}
func showView(view: UIView) {
view.userInteractionEnabled = true
view.hidden = false
}
}
extension UIView {
func hideView() -> UIView {
self.userInteractionEnabled = false
self.hidden = true
return self
}
func showView() -> UIView {
self.userInteractionEnabled = true
self.hidden = false
return self
}
}
ContainerAViewController:
class ContainerAViewController: UIViewController {
#IBOutlet weak var textFieldA: UITextField!
static let sharedInstance = ContainerAViewController()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
}
ContainerBViewController:
class ContainerBViewController: UIViewController {
#IBOutlet weak var textFieldB: UITextField!
static let sharedInstance = ContainerBViewController()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
}
When I tap the button, it gives me the following error:
fatal error: unexpectedly found nil while unwrapping an Optional value
Can somebody please help?
You should not try to manipulate another view controller's views. That violates the principle of encapsulation, an important principle in object-oriented development.
You should give your child view controllers (ContainerAViewController and ContainerBViewController) string properties, and have the code for those view controllers set that string property when the user enters text into the view controllers' text fields.
Ignoring that design flaw, your code doesn't make sense. You show your CLASS as ContainerAViewController, and yet your buttonTapped method is trying to ask a ContainerAViewController singleton for the value of a text field. That makes no sense.
You want to have properties in your parent view controller that point to your child view controllers.
You should implement a prepareForSegue method in your parent view controller, and in that prepareForSegue method, look for the embed segues that fire when the child view controllers are loaded. When that happens you should set your properties that point to the child view controllers.
I've got view controller (using Storyboards if matters). Controller got custom view inside it let's call it AView. The view is laid out on storyboard as UIView object with custom class set. AView's contents are on separate XIB because I need this highly reusable. Here's how code looks like:
class VC: UIViewController {
#IBOutlet weak var aView: AView!
override func viewDidLoad() {
super.viewDidLoad()
aView.setup(false) //doesn't work
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
aView.setup(false) //doesn't work
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
aView.setup(false) //do work but glitches
}
}
class AView: UIView {
required init?(coder aDecoder: NSCoder) {
//init stuff: loading nib, adding view from it
}
#IBOutlet weak var someView: UIView! //this view has all constraints which are required and additional rightConstraint which is inactive, for future use
#IBOutlet var leftConstraint: NSLayoutConstraint!
#IBOutlet var rightConstraint: NSLayoutConstraint!
func setup(shouldBeOnLeft: Bool) {
leftConstraint.active = true
rightConstraint.active = false
self.layoutIfNeeded()
}
}
I need to setup this view before it appears, based on some parameters. I'm modifying only its internal content from inside. If I call aView.setup(shouldBeOnLeft:) in viewDidLoad or viewWillAppear constraints don't update or maybe do but I don't see changes. If I move it to viewDidAppear it works but obviously I see misplaced views for a while (state before setup).
The question is: how to get it work as intended and without view's manipulation form view controller and independent on how and where setup method is called unless it's inside or right after VC's viewDidLoad? Only thing that VC needs to know is to call setup with parameter.
You should call it inside viewDidLayoutSubviews(), at this point it have set the view parameters, and you can manipulate the view before is presented to the user (so, no glitches)
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIViewController_Class/#//apple_ref/occ/instm/UIViewController/viewDidLayoutSubviews
Discussion
Called to notify the view controller that its view has just laid out its subviews.
OK I figured that out - but I don't actually like it in 100%
class VC: UIViewController {
#IBOutlet weak var aView: AView!
override func viewDidLoad() {
super.viewDidLoad()
aView.setup(false) //now it does work
}
}
class AView: UIView {
required init?(coder aDecoder: NSCoder) {
//init stuff: loading nib, adding view from it
}
#IBOutlet weak var someView: UIView! //this view has all constraints which are required and additional rightConstraint which is inactive, for future use
#IBOutlet var leftConstraint: NSLayoutConstraint!
#IBOutlet var rightConstraint: NSLayoutConstraint!
func setup(shouldBeOnLeft: Bool) {
self.yesIfLeft = shouldBeOnLeft
}
private var yesIfLeft = false {
didSet{
self.updateLayout()
}
}
private func updateLayout() {
leftConstraint.active = self.yesIfLeft
rightConstraint.active = !self.yesIfLeft
self.layoutIfNeeded()
}
override func layoutSubviews() {
super.layoutSubviews()
self.updateLayout()
}
}
Whole point is to keep reference to settings that cause layout change and update them also in layoutSubviews(). Doing this way VC doesn't have to know about internals of AView (that's a big plus for me) but requires that AView holds it's configuration which is some extra work to do. I'm not marking this answer as accepted for now because maybe someone will have some better idea.
I would like to make a UI that have label, table view and one button click. When click on the button, we pop up a half screen view that have lots of buttons. I want user can still click on the rest of the screen also.
So i use the approach that suggest in the post
How To Present Half Screen Modal View?
Method 2: to animate a UIView which is of size half of the existing view.
Then you have to simply follow animation of the UIView.
Here as it is just a UIView that will be added as subview to existing view, you will be able to touch the rest of the screen.
As i am newbie to the ios and swift, I would like to get some suggestions.
Now i am successfully add as subview and show in the half of the screen.
How can i implement to let subview click button result show on parent view label text?
I am thinking about parent.xib and subview.xib have the same UIVeiwController.swift. Then i can #IBOutlet and #IBAction to the same controller swift file and update the result. But don't know it is the accpetable way to do?
If not, how can the subViewController send result/event to the parent view and update in the parent view component?
You could use delegation. This keeps your view controllers decoupled, i.e. prevents the child from having a reference to its parent, which allows other view controllers to interact with the modal view controller in the same way.
class ParentViewController : UIViewController, ModalViewControllerDelegate {
#IBOutlet weak var label: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
let modalViewContorller = ModalViewController()
modalViewContorller.delegate = self
self.presentViewController( modalViewContorller, animated: true, completion: nil )
}
func modalViewControllerDidProduceResult( modalViewController: ModalViewController, result: String ) {
self.label.text = result
}
}
protocol ModalViewControllerDelegate {
func modalViewControllerDidProduceResult( modalViewController: ModalViewController, result: String )
}
class ModalViewController: UIViewController {
var delegate: ModalViewControllerDelegate?
#IBAction func buttonClicked( sender: AnyObject? ) {
delegate?.modalViewControllerDidProduceResult( self, result: "Hello!" )
}
}
You could also use a closure, which in Swift provides a more concise syntax.
class ParentViewController : UIViewController {
#IBOutlet weak var label: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
let modalViewContorller = ModalViewController()
self.presentViewController( modalViewContorller, animated: true, completion: nil )
modalViewContorller.resultBlock = { (result: String) in
self.label.text = result
}
}
}
class ModalViewController: UIViewController {
var resultBlock: ((String) -> ())?
#IBAction func buttonClicked( sender: AnyObject? ) {
self.resultBlock?( "Hello!" )
}
}