I am developing an app on XCode using Swift 2 and have run into an error with my UI. I have placed a UILabel, UITextView, and UITextField on the View Controller of interest.
Before inputting the UITextField, everything worked fine. I inserted the text field and assigned the proper constraints and when simulating the app, clicking on the text field will cause all of the UI elements to disappear or the alpha to go instantaneously to zero (I'm not sure which of the two is actually happening).
I'm not receiving any feedback via the console and the app itself does not crash.
Here is my code:
//Declare the following UI objects to be manipulated
#IBOutlet weak var labelOneTitle: UILabel!
#IBOutlet weak var textDescription: UITextView!
#IBOutlet weak var fieldInput: UITextField!
override func viewDidLayoutSubviews() {
//Set UI object alphas to zero prior to loading the view
labelOneTitle.alpha = 0
textDescription.alpha = 0
fieldInput.alpha = 0
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func viewDidAppear(animated: Bool) {
UIView.animateWithDuration(2) { () -> Void in
self.labelOneTitle.alpha = 1
}
UIView.animateWithDuration(3) { () -> Void in
self.textDescription.alpha = 1
}
UIView.animateWithDuration(3) { () -> Void in
self.fieldInput.alpha = 1
}
}
It might be the UIViewController.viewDidLayoutSubviews rechecking all the constraints and reconfiguring the view as per the the class reference:
When the bounds change for a view controller's view, the view adjusts the positions of its subviews and then the system calls this method. However, this method being called does not indicate that the individual layouts of the view's subviews have been adjusted. Each subview is responsible for adjusting its own layout.
Your view controller can override this method to make changes after
the view lays out its subviews. The default implementation of this
method does nothing.
When you click on the UITextField the keyboard appears (Probably) and thus recalling the method to set the alpha of all your UI Elements to 0.
If you'd move setting the alpha's to 0 to the viewDidLoad() than the problem might go away.
Related
OK, so here's the relevant bit of my code:
class MyViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var questionView: QuestionView!
#IBOutlet weak var answerView: AnswerView!
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var tableViewTopConstraint: NSLayoutConstraint! // Active at start, attaches tableView.topAnchor to questionView.bottomAnchor
#IBOutlet weak var tableViewTopAlternateConstraint: NSLayoutConstraint! // Inactive at start, attaches tableView.topAnchor to answerView.bottomAnchor
func submitAnswer() {
tableViewTopConstraint.isActive = false
tableViewTopAlternateConstraint = true
}
func newQuestion() {
tableViewTopConstraint.isActive = true
tableViewTopAlternateConstraint = false
}
}
I'm building a question/answer type of app and the two subviews are different sizes for design reasons. I have it set so that it toggles between the two subviews depending on the current state (toggling back for a new question when the user taps to advance), and it works fine...
...until I go to a different tab on the UITabView that encloses everything (e.g. to change the settings then resume testing)
The moment the tab changes it's like the NSLayoutConstraint outlets no longer exist. The orders to change them still process (I've verified this in the console), but they do nothing.
I've tried declaring the outlets as strong (there was no difference in behavior)
I've removed the outlets and managed them in code (it worked the first time, but afterward it stretched the shorter QuestionView's height to match tableView's new top position instead of moving tableView up to the bottom of QuestionView as was intended)
I've tried implementing a UITabViewController so that I could call tabBar(_:didSelect:) and replace the view controller with a brand new instance of MyViewController() whenever I switch back to that tab (It loads the first time, but when I try to switch tabs it finds nil when accessing any of the IBOutlets...even though I didn't tap on the tab for the testing view and filtered by item.tag when replacing the existing view controller)
Any suggestions on how else to attack this problem?
First, don't set #IBOutlet properties to weak. I know that's the default, but it's not correct.
If you have this:
#IBOutlet weak var tableViewTopAlternateConstraint: NSLayoutConstraint!
connected to a constraint via Storyboard, and you do this:
tableViewTopAlternateConstraint.isActive = false
you just removed that constraint from existence.
In addition, if you have two different Top constraints in Storyboard, you should be getting an Error indicator as they cannot be satisfied.
Better to either change the Priorities, or use a single constraint and change the .constant value.
So, two constraints in Storyboard:
set the "default" constraint to Priority: High (750)
set the "alternate" constraint to PriorityL Low (250)
and your code becomes:
func submitAnswer() {
tableViewTopConstraint.priority = .defaultLow
tableViewTopAlternateConstraint.priority = .defaultHigh
}
func newQuestion() {
tableViewTopAlternateConstraint.priority = .defaultLow
tableViewTopConstraint.priority = .defaultHigh
}
I've read a lot of material on this topic but most of them create custom view programatically.
Is it possible to use InputAccessoryView with a custom view created in IB? In storyboard I've added textInputView, inside which I've added text view and send button etc as seen in the screenshot below.
I've following code so which removes Table View for some reason so I can't get it working. I've added tableview in the storyboard.
I've shown here only InputAccessoryView related code.
class InputAccViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var textView: UITextView!
#IBOutlet weak var textInputView: UIView!
// other code
override var canBecomeFirstResponder: Bool { return true }
override var inputAccessoryView:UIView {
get{
return self.textInputView
}
}
override func viewDidLoad() {
super.viewDidLoad()
textInputView.removeFromSuperview()
// other code
}
// other code tableview delegates etc...
}
Left screenshot is with the accessory view code which doesn't show table view. If I comment out accessory view related code it does show table view as in the right screenshot.
Seems you might be constraining the UITableView's bottom to the textInputView's top. When you are setting the textInputView as the inputAccessoryView of the UIViewController this no longer works as expected. When setting the textInputView as the inputAccessoryView make sure you constraint the bottom of UITableView to the bottom of UIViewController's view.
I am looking for a way to override what happens when a user does an accessibility scroll (three finger scroll with VoiceOver enabled) to the left or right in a WKWebView on iOS. I tried subclassing WKWebView and overriding goBack(), goForward(), and accessibilityScroll(...). My subclass methods do not get called. I also tried implementing accessibilityScroll(...) in my view controller, but that method did not get called either.
Ideally I want to continue letting WKWebView handle three finger scrolls up and down, but add my own behavior when a user three finger scrolls to the left or right.
You could try capturing the accessibility scroll of the WebViews scrollView
class ViewController: UIViewController {
#IBOutlet weak var webView: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
webView.scrollView.delegate = self
}
}
extension ViewController: UIScrollViewDelegate {
override func accessibilityScroll(_ direction: UIAccessibilityScrollDirection) -> Bool {
// Your code here
}
}
I have a bottomView with opacity set to 0.65, and embedded in that View I have 5 buttons - which also gets the 0.65 opacity attribute - but How do I make the buttons get rid of the opacity?
I want the buttons to be very clear
I have tried to make outlets of the View and the Buttons and set the buttons to the front - view, but it doesn't change the appearance of the button??
#IBOutlet weak var bottomView: UIView!
#IBOutlet weak var findVejOutlet: UIButton!
#IBOutlet var superViewOutlet: UIView!
#IBAction func findVejButton(_ sender: Any) {
superViewOutlet.bringSubview(toFront: findVejOutlet)
}
override func viewDidLoad() {
super.viewDidLoad()
settingView()
}
func settingView(){
bottomView.bringSubview(toFront: findVejOutlet)
}
If you set the opacity of a view to a value of less than 1, it makes all the contents of a view partly transparent (including subviews). You can't change that, and opacity has nothing to do with the front-to-back ordering of the views.
You either need to make the parent view fully opaque and the non-button subviews partly transparent, or remove the buttons from the translucent view and instead put them in the common parent view.
What I'm trying to do is to create something similar to the "find on page" search function in Safari on iPad.
I'm using a UIToolbar with some items in it and attached it to the keyboard by setting it as an inputAccessoryView on the UITextField. Works like a charm, but there is one thing I can't figure out. In Safari, when you search for something, the keyboard disappears but the tool bar remains on the bottom of the screen.
Does anyone have a clue on how to accomplish this? The only solution I can think of is to respond to a keyboard dismissed event and then pull out the UIToolBar and create a custom animation that moves it to the bottom of the screen. But this is hacky. I am looking for a more elegant solution. Something that can make me decide what to do with the input accessory view when the keyboard gets dismissed.
It's done like this:
Assign your UIToolbar to a property in your view controller:
#property (strong, nonatomic) UIToolbar *inputAccessoryToolbar;
In your top view controller, add these methods:
- (BOOL)canBecomeFirstResponder{
return YES;
}
- (UIView *)inputAccessoryView{
return self.inputAccessoryToolbar;
}
And then (optionally, as it usually shouldn't be necessary), whenever the keyboard gets hidden, just call:
[self becomeFirstResponder];
That way, your inputAccessoryToolbar will be both your view controller's and your text view's input accessory view.
I've ended up with UIToolBar that is not assigned as input accessory view, and slide up and down on UIKeyboardWillShowNotification / UIKeyboardWillHideNotification
Update to Swift 4, based on prior answers. If you add toolbar via storyboards you can do this
class ViewController: UIViewController {
#IBOutlet weak var textField: UITextField!
#IBOutlet var toolbar: UIToolbar!
override var canBecomeFirstResponder: Bool {
get {
return true
}
}
override var inputAccessoryView: UIView {
get {
return self.toolbar
}
}
override func viewDidLoad() {
super.viewDidLoad()
textField.inputAccessoryView = toolbar
}
}
In this case, whenever text field resigns first responder, it defaults first responder to main view. Keep in mind, you might want to explicitly resign first responder, and set main view as first responder if there are multiple UI elements and first responder defaults to undesired view after resignation.
Adding to #arik's answer, here is the Swift version:
class ViewController: UIViewController {
#IBOutlet var textField: UITextField!
// Input Accessory View
private var inputAccessoryToolbar: UIToolBar?
override func canBecomeFirstResponder() -> Bool {
return true
}
override var inputAccessoryView: UIView? {
return inputAccessoryToolbar
}
override func viewDidLoad() {
super.viewDidLoad()
inputAccessoryToolbar = UIToolbar(frame: CGRectMake(0, 0, view.frame.size.width, 50))
textField.inputAccessoryView = inputAccessoryToolbar
}
// UITextFieldDelegate
func textFieldShouldReturn(textField: UITextField) -> Bool {
becomeFirstResponder()
return true
}
}
Thanks for the clean solution!
You may also need to work around the bug with the inputAccessoryView not respecting the safe area margins and thus not making room for home indicator thing on iPhone X: iPhone X how to handle View Controller inputAccessoryView?
I found the easiest solution when you have a UIToolbar from a xib and you are also using that UIToolbar as the inputAccessoryView of a text field is to embed the toolbar in a UIView when you return it from your overridden inputAccessoryView, and make the containing UIView taller by the safeAreaInsets.bottom. (Other solutions suggest constraining the bottom of the toolbar to the safe area in a subclass, but this leads to constraint conflicts and also means the area under the toolbar is the wrong colour.) However, you have to also bear in mind that the text field can have focus even when there is no keyboard on the screen (for instance if there is an external keyboard), so you need to change the inputAccessoryView of the text view to this toolbar-within-a-UIView in that case as well. In fact it will probably make things simpler to just always use the containing view and adjust the size of it appropriately. Anyway, here's my override of inputAccessoryView:
override var inputAccessoryView: UIView? {
if toolbarContainerView == nil {
let frame=CGRect(x: toolBar.frame.minX, y: toolBar.frame.minY, width: toolbar.frame.width, height: toolBar.frame.height+view.safeAreaInsets.bottom)
toolbarContainerView = UIView(frame: frame)
}
if (toolbar.superview != toolbarContainerView) {
//this is set to false when the toolbar is used above the keyboard without the container view
//we need to set it to true again or else the toolbar will appear at the very top of the window instead of the bottom if the keyboard has previously been shown.
toolbar.translatesAutoresizingMaskIntoConstraints=true
toolbarContainerView?.addSubview(toolbar)
}
return toolbarContainerView
}
It would probably be a good idea to override viewSafeAreaInsetsDidChange to adjust the size of toolbarContainerView in that case, too.