I want to change the place where the cancel button appears instead of appearing on the right side as in the image I want it to appear on the left side of the search bar.
You cannot change the position of the default SearchBar and Search Display controller.
For your requirement, you need to create your own custom Search Bar with Cancel as you desire.
Hope it helps..
One way to put the cancel button on the left is to override the effectiveUserInterfaceLayoutDirection of the Search Bar (iOS 10+).
final class CustomSearchController: UISearchController {
private var customSearchBar = CustomSearchBar()
override var searchBar: UISearchBar { customSearchBar }
}
final class CustomSearchBar: UISearchBar {
override var effectiveUserInterfaceLayoutDirection: UIUserInterfaceLayoutDirection {
// Overriding this property swaps the layout at the top level without swapping the layout for the subviews
super.effectiveUserInterfaceLayoutDirection.flipped
}
override func willMove(toSuperview newSuperview: UIView?) {
super.willMove(toSuperview: newSuperview)
// Set and unset the semanticContentAttribute so the effectiveUserInterfaceLayoutDirection is reevaluated.
semanticContentAttribute = .forceLeftToRight
semanticContentAttribute = .unspecified
}
}
extension UIUserInterfaceLayoutDirection {
var flipped: UIUserInterfaceLayoutDirection {
switch self {
case .rightToLeft: return .leftToRight
case .leftToRight: return .rightToLeft
#unknown default: return .rightToLeft
}
}
}
This should put the cancel button on the left for LTR languages, and on the right for RTL languages.
Result in English:
Related
What is the best way to align right bar button items with left title in UINavigationBar ?
(now I have the icons at the top right corner)
maybe there is a more correct and optimal solution, but it is suitable for me.
Do not be alarmed by the syntax, I use SnapKit
Create button or view
lazy private var settingsButton = UIButton().then {
$0.setImage(Image.settings, for: .normal)
}
override viewDidAppear(_ animated: Bool)
navigationController?.navigationBar.subviews.forEach { subview in
let stringFromClass = NSStringFromClass(subview.classForCoder)
guard stringFromClass.contains("UINavigationBarLargeTitleView") else { return }
subview.subviews.forEach { label in
guard label is UILabel else { return }
subview.addSubview(settingsButton)
settingsButton.snp.makeConstraints{
$0.top.equalTo(label)
$0.right.equalToSuperview().offset(-14)
$0.height.width.equalTo(35)
}
}
}
set UIScrollViewDelegate and in scrollViewDidScroll method get ahead of the navigationBar state and then hide / show the UIBarButtonItem
Result
screenshot
when launching the iOS share extension the textView will by default already be selected / entered. (the keyboard will be visible and the textView will be in edit mode)
I don't want this to happen, how do I programatically exit the textView
override func viewDidLoad() {
self.textView.exit() // obviously doesn't work
}
I see tons of posts about how to exit when user press enter on the keyboard, I do not want to do it "when something delegate" I just want the textview to not be in edit mode when the extension is launched (on viewDidLoad).
I have also tried (as suggested in other post)
self.textView.endEditing(true)
which did not hide the keyboard or exit the textView
You can call textView.resignFirstResponder() in presentationAnimationDidFinish
class ShareViewController: SLComposeServiceViewController {
var textViewTintColor: UIColor?
override func viewDidLoad() {
super.viewDidLoad()
// hide cursor which appears during presentation animation
self.textViewTintColor = self.textView.tintColor
self.textView.tintColor = .clear
}
override func presentationAnimationDidFinish() {
super.presentationAnimationDidFinish()
guard let tintColor = self.textViewTintColor else { return }
self.textView.resignFirstResponder()
// reset cursor
self.textView.tintColor = tintColor
self.textViewTintColor = nil
}
}
I want to add a banner to the navigation bar, but by increasing the height of it. I want to copy the design and behaviour of an artist page in the Apple Music app:
It behaves just like a normal Large Title would, except for that it has been moved down, it has a sticky UIImageView behind it and it returns its background when the user scrolls down far enough. You can fire up Apple Music, search for an artist and go to their page to try it out yourself.
I've tried a bunch of things like setting the frame on the UINavigationBarLargeTitleView, and the code from this answer: https://stackoverflow.com/a/49326161/5544222
I already got a hold of the UINavigationBarLargeTitleView and its UILabel using the following code:
func setLargeTitleHeight() {
if let largeTitleView = self.getLargeTitleView() {
if let largeTitleLabel = self.getLargeTitleLabel(largeTitleView: largeTitleView) {
// Set largeTitleView height.
}
}
}
func getLargeTitleView() -> UIView? {
for subview in self.navigationBar.subviews {
if NSStringFromClass(subview.classForCoder).contains("UINavigationBarLargeTitleView") {
return subview
}
}
return nil
}
func getLargeTitleLabel(largeTitleView: UIView) -> UILabel? {
for subview in largeTitleView.subviews {
if subview.isMember(of: UILabel.self) {
return (subview as! UILabel)
}
}
return nil
}
Initially put a view with image and label and play button. Then clear the navigation bar it will show the image below it by
self.navigationController!.navigationBar.setBackgroundImage(UIImage(), for: .default)
self.navigationController!.navigationBar.shadowImage = UIImage()
self.navigationController!.navigationBar.isTranslucent = true
later when you scroll up you have to handle it manually and again show the
navigation with title.
I have a view controller that takes up the whole screen from top to bottom. I would like to hide the home bar indicator on the bottom of the screen on iPhone X devices.
How can I do this in iOS 11?
You should override prefersHomeIndicatorAutoHidden in your view controller to achieve that:
override var prefersHomeIndicatorAutoHidden: Bool {
return true
}
There is another alternative. If you are looking for the behavior where the indicator dims, then when the user swipes up it activates, and when they swipe up again the home action is invoked (I.E., two swipes are needed to invoke), then the answer is here: iPhone X home indicator behavior. The short of it is to override on your UIViewController:
override var preferredScreenEdgesDeferringSystemGestures: UIRectEdge {
return UIRectEdge.bottom
}
prefersHomeIndicatorAutoHidden only hides the indicator, but will not suppress the gesture.
And you will get what you want (If I understand your comments correctly - your question and the selected answer seem to imply the other answer).
If your window?.rootViewController is a UITabBarController or UINavigationController, just inherit it and add two function as follows,
override var prefersHomeIndicatorAutoHidden: Bool {
return true
}
//#available(iOS 11, *)
override var childViewControllerForHomeIndicatorAutoHidden: UIViewController? {
return nil
}
Implement -(BOOL)prefersHomeIndicatorAutoHidden in your UIViewController and return YES.
Read more https://developer.apple.com/documentation/uikit/uiviewcontroller/2887510-prefershomeindicatorautohidden.
I tried to set it and return true only when I am in full-screen :
override var prefersHomeIndicatorAutoHidden: Bool { isNavigationBarAndTabBarHidden }
but it doesn't seem to work... isNavigationBarAndTabBarHidden is a custom variable tied to my fullscreen extension.
Edit: We need to call setNeedsUpdateOfHomeIndicatorAutoHidden every time we update prefersHomeIndicatorAutoHidden's value.
var isNavigationBarAndTabBarHidden = false {
didSet {
setNeedsUpdateOfHomeIndicatorAutoHidden()
}
}
override func prefersHomeIndicatorAutoHidden() -> Bool {
return true
}
I suppose you can add this method in your AppDelegate for hide home indicator on all of your ViewControllers.
I have an inputAccessoryView for text input in a chat app. I implemented the inputAccessoryView using the following:
override var inputAccessoryView: UIView? {
get {
setupInputToolbar()
return inputToolbar
}
}
override var canBecomeFirstResponder: Bool {
return true
}
This occurs in a viewController which is a childViewController. I use a segmented control to display this chat viewController with the inputAccessoryView.
The problem I'm having is the inputAccessoryView will only display if I place self.becomeFirstResponder() in the viewDidAppear() of the chat viewController (child).
If I omit self.becomeFirstResponder() or place it in viewDidLoad() or viewWillAppear(), the inputAccessoryView does not display.
The problem with having it in viewDidAppear() is that it displays with an animation after the view is already on screen which is not what I want.
I accomplished this by first adding a viewHasPerformedSubviewLayoutAtLeastOnce property to the ViewController.
private var viewHasPerformedSubviewLayoutAtLeastOnce = false
Making canBecomeFirstResponder dependent on the above property.
override var canBecomeFirstResponder: Bool {
return viewHasPerformedSubviewLayoutAtLeastOnce
}
Then updating that property in viewDidLayoutSubviews() and becoming first responder inside a UIView.performWithoutAnimation block.
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if viewHasPerformedSubviewLayoutAtLeastOnce == false {
viewHasPerformedSubviewLayoutAtLeastOnce = true
UIView.performWithoutAnimation { becomeFirstResponder() }
}
}
It all feels a little hacky, but it results in the view appearing without any animations.