Insert subview above inputAccessoryView - ios

My problem is this:
I have a UIViewController with an input accessory view:
class ChatViewController: UIViewController {
override var inputAccessoryView: UIView! {
get { return customToolbar }
}
}
I would like to be able to add a subview above that inputAccessoryView, something like this:
let customView = UIView()
customView.backgroundColor = UIColor.blueColor()
self.view.insertSubview(customView, aboveSubview: self.inputAccessoryView)
But for some reasons, the inputAccessoryView is always on top.
I have thought of hiding it each time i want to add a subview (full screen for instance), but this is not super clean, and I'd have to put it back each time i remove my subview.
Here's what I have right now, I just want my toolbar to be behind the transparent overlay.

Try to add your transparent overlay to keyWindow in the Objective-C it's look like
[[[UIApplication sharedApplication].windows lastObject] addSubview :customView];

Related

Instagram Reels Comments view in swift

In my app, I have a similar view like Instagram reels where video plays and at bottom there is profile picture, description of video, like button and comments button.
When I click comments button, I would like to show comments table view controller covering upto half screen, with comments and inputbar accessoryview at bottom like below:
half screen view controller
When I click inside inputbar accessoryview, the comments view controller show cover fullscreen and should show keyboard like below:
full screen view controller
Please advise me on how to achieve this behaviour.
You could create a new ViewController in storyboard or code, then set the set self.view.backgroundColor = .clear (alternatively, you could change the Opacity of the backgroundColor of the view in storyboard by using a custom color).
Next, place a UIView on the storyboard. Create an #IBOutlet for the height constraint of the view. You can get the height of the view (the ViewController's UIView, not the one you just placed) by using
let viewHeight = self.view.frame.size.height
self.commentsView.setHeight(viewHeight/2)
Make sure to include this extension in your project for setHeight:
extension UIView {
func setHeight(_ h:CGFloat, animateTime:TimeInterval?=nil) {
if let c = self.constraints.first(where: { $0.firstAttribute == .height &&
$0.relation == .equal }) {
c.constant = CGFloat(h)
if let animateTime = animateTime {
UIView.animate(withDuration: animateTime, animations:{
self.superview?.layoutIfNeeded()
})
}
else {
self.superview?.layoutIfNeeded()
}
}
}
}

iOS 13 UIViewController modal presentation shadow

I have modaly presented view controller in iOS >=13. Root view has clear background:
view.backgroundColor = .clear
Child view with white background has some top offset like this:
All is ok, but when I try to dismiss it by swipe down I see slightly visible shadow of presented view controller:
Is it posible to remove this shadow on modal presentation?
UPDATE: Upon further investigation, this does not appear to be something that can be changed. It's a private UIKit View setup by iOS and is a new addition in iOS 13. See 19:50 at https://developer.apple.com/videos/play/wwdc2019/224/
For my own apps/games I'll be looking to create a custom UIModalPresentationStyle to achieve the look I want.
You can also alleviate from this by simply presenting as .fullScreen or another presentation style instead of this new sheet method.
I have been trying to find the answer to this myself. So far I have only found that by setting the layer.shadowColor to clear it fixes this but only on iPhone. I cannot find how to fix this on iPad.
override func viewDidLoad() {
view.layer.shadowColor = UIColor.clear.cgColor
}
I have solution for you
extension UIViewController {
func removeBackgroundForParents() {
var superview = view.superview
while superview != nil {
superview?.layer.backgroundColor = UIColor.clear.cgColor
superview?.layer.shadowColor = UIColor.clear.cgColor
superview = superview?.superview
}
}
}
And use it in your view controller.
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
removeBackgroundForParents()
}

Get the first UIButton in a UIStackView

I'm working on my view and I'm having an issue with getting a shadow around a button within the stack view. Most of the work I have done has been within the storyboard directly.
Here is the method I am using to apply the shadow to the view
func addShadow(to view: UIView) {
view.layer.shadowColor = shadowColor
view.layer.shadowOpacity = shadowOpacity
view.layer.shadowOffset = shadowOffset
if let bounds = view.subviews.first?.bounds {
view.layer.shadowPath = UIBezierPath(rect: bounds).cgPath
}
view.layer.shouldRasterize = true
}
and this is how I'm finding the button within the view from ViewController.swift
for subview in self.view.subviews {
if subview.isKind(of: UIButton.self) && subview.tag == 1 {
addShadow(to: subview)
}
}
I know the problem stems from the stack view and the UIView inside of the stack view that holds the button. (self.view > UIStackView > UIView > [UIButton, UILabel])
I know I could do this with recursion in the for-loop but I'm trying to be a little more precise to optimize performance and would prefer to add the shadows in one shot.
You have a few options:
add the shadow in the storyboard itself
add an outlet to the button, then add shadow in code
add the button to a collection, then enumerate over the collection adding shadows
recursively add the shadows (this isn't going to hit performance nearly as hard as you're thinking, adding the shadows hurts performance more than doing this recursively)
You are correct in that the button is a view on the stack view, so your for loop doesn't hit the button directly to add a shadow to it.
The easiest way to solve this is by far the recursive way, or something like this:
func addShadowsTo(subviews: [UIView]) {
for subview in subviews {
if subview.isKind(of: UIButton.self) && subview.tag == 1 {
addShadow(to: subview)
}
if let stackView = subview as? UIStackView {
addShadowToSubviews(subviews: stackView.subviews)
}
}
}
func viewDidload() {
super.viewDidLoad()
addShadowsTo(subviews: view.subviews)
}
If you want some instructions on how to do any of the other ways, just comment.

Is it possible to pin a UIView's top to the bottom of the navigation bar?

I'm trying to position my UIView to be 20pt under the navigation bar, but when I set it relative to the view on the view controller it's still under the navigation bar at 20pt, and I don't want to hardcode it.
Is it possible to position it away from the navigation bar?
To do this programmatically use the topLayoutGuide of your view controller:
override func viewDidLoad() {
...
myView.topAnchor.constraint(equalTo: self.topLayoutGuide.bottomAnchor).isActive=true
...
}
Try adding contraint with TopLayoutGuide,
The main difference between TopLayoutGuide and self.view is, top layout guide starts with bottom of status bar + bottom of navigation bar(if exist), but self.view always start from (0,0) coordinate in iOS 7 for translucent navigation bar.
So in your case you should try pinning from top layout guide.
From iOS 11.0 programmatically you can use view.safeAreaLayoutGuide:
override func viewDidLoad() {
...
label.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive=true
}
Pin to the top safe area layout guide. Code if your are using SnapKit:
titleLabel.snp.makeConstraints { make in
make.top.equalTo(view.safeAreaLayoutGuide.snp.top).offset(20)
make.leading.equalToSuperview()
}
So your UIView sits within a UIViewController, correct?
When you define your initial inner UIView to that UIViewController, can you set its coordinates to be 20px off the top of the ViewController?
For example:
//
// ViewControllerEx.swift
//
import UIKit
class ViewControllerExample: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
var innerContainer = UIView(frame: CGRectMake(0, 20, self.view.bounds.width, self.view.bounds.height-20))
self.view.addSubview(innerContainer)
}
}

Leaving inputAccessoryView visible after keyboard is dismissed

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.

Resources