How do I actually create a custom UISearchBar? - swift

I have been banging my head against a wall for a day now trying to figure out how to create a custom UISearchBar so I can get rid of the stock black search bar UIKit thinks I want. I've tried a few 'solutions':
1. Customizing the appearance in the AppDelegate
//default background color for search bars
UISearchBar.appearance(whenContainedInInstancesOf: [UISearchController.self]).tintColor = UIColor(hexString: "#394C53")
This didn't work
2. Subclassing UISearchController and UISearchBar
I followed this tutorial which didn't work
3. Looping through subviews
let results = DestinationSearchResultsTableViewController()
results.mapView = map
results.handleMapSearchDelegate = self
destinationSearch = UISearchController(searchResultsController: results)
destinationSearch!.searchBar.placeholder = "Where do you want to go?"
if #available(iOS 11, *) {
navigationItem.searchController = destinationSearch
if let textfield = destinationSearch!.searchBar.value(forKey: "searchField") as? UITextField {
if let backgroundview = textfield.subviews.first {
// Below is not what I want but it works
backgroundview.backgroundColor = UIColor.white
backgroundview.layer.cornerRadius = 10
backgroundview.clipsToBounds = true;
}
}
} else {
self.navigationController?.pushViewController(destinationSearch!, animated: true)
}
destinationSearch!.delegate = self
destinationSearch!.searchResultsUpdater = results as UISearchResultsUpdating
This actually kinda works. It works really well if I use the UIColor predefined colors like UIColor.white UIColor.red etc. but it works really terribly if I want to use a specific color like this: I tried using RGB and hex representations of the color above, but all I get is black.
Here is what I want:
I want to create my own UISearchBar with a left-aligned label and content textfield to the right as I have shown above.
Thanks

Related

How to make UITabBar blurry, in Swift

I am trying to make UITabBar look blur.
I am trying to make something like this in this image
But my view now looks like this
This is my view for tabbar
I tried this code in UITabbarController -
Code:
class TabBarViewController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
configureTabbar()
}
func configureTabbar(){
let blurEffect = UIBlurEffect(style: .dark)
let vibrancyEffect = UIVibrancyEffect(blurEffect: blurEffect)
let vibrancyView = UIVisualEffectView()
vibrancyView.frame = tabBar.bounds
vibrancyView.autoresizingMask = .flexibleWidth
vibrancyView.effect = vibrancyEffect
tabBar.insertSubview(vibrancyView, at: 0)
tabBar.isTranslucent = true
tabBar.backgroundImage = UIImage()
tabBar.backgroundColor = .clear
tabBar.barStyle = UIBarStyle.black
tabBar.barTintColor = UIColor.clear
}
System bars such as UINavigationBar, UITabBar & UIToolbar are translucent by default and you don't need to add anything extra to get that effect.
You just need to make sure that your view extends it's content under system bars. You can go to storyboard and make sure the Extend Edges - Under Bottom Bars is checked for your UIViewController that you plan to see this effect on.

How to make a window with rounded corners

I could find a good solution to make a window with rounded corners
https://github.com/lukakerr/NSWindowStyles
Section: 6. Vibrant background with border radius and no titlebar
let visualEffect = NSVisualEffectView()
visualEffect.translatesAutoresizingMaskIntoConstraints = false
visualEffect.material = .dark
visualEffect.state = .active
visualEffect.wantsLayer = true
visualEffect.layer?.cornerRadius = 16.0
window?.titleVisibility = .hidden
window?.styleMask.remove(.titled)
window?.backgroundColor = .clear
window?.isMovableByWindowBackground = true
window?.contentView?.addSubview(visualEffect)
guard let constraints = window?.contentView else {
return
}
visualEffect.leadingAnchor.constraint(equalTo: constraints.leadingAnchor).isActive = true
visualEffect.trailingAnchor.constraint(equalTo: constraints.trailingAnchor).isActive = true
visualEffect.topAnchor.constraint(equalTo: constraints.topAnchor).isActive = true
visualEffect.bottomAnchor.constraint(equalTo: constraints.bottomAnchor).isActive = true
I find that it only works if I put it in the viewWillAppear or in the viewDidLoad with a delay. In any case, when I get the border-radius and the vibrant background I cannot see anything that is in the window, for instance, a simple label with the text test. (I tried to put that text on the storyboard or by code)
#IBOutlet weak var label1: NSTextField!
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0){
self.label1.stringValue = "test text"
}
How to make a label visible with this code?
(or as second option, how to make a window with round corners, no bar, background semi-transparent or vibrant and a label non-transparent on top or inside?)
(I know how to make the title bar transparent and remove the buttons and title. But that is not a good solution for what I need because the bar is still there and creates a space that makes problems)
The problem with the label is you're adding NSVisualEffectView above it. You could instead try adding it below:
view.addSubview(visualEffect, positioned: .below, relativeTo: label1)
But be careful you add it only once: viewWillAppear can be called multiple times.

How to display a UITextView programmatically?

I am trying to display a basic UITextView programmatically. The problem is, if I want to display a UILabel with the same constraints and settings, it's all fine, I can have it displayed. However, if I change it to a UITextView, it just disappears. I created an empty project, just to check it, but still, no avail, just does not display.
Here's my code that works fine for a UILabel:
view = UIView()
view.backgroundColor = .systemBackground
explanationView = UILabel()
explanationView.layer.zPosition = 1
explanationView.translatesAutoresizingMaskIntoConstraints = false
explanationView.numberOfLines = 10
view.addSubview(explanationView)
NSLayoutConstraint.activate([
explanationView.centerXAnchor.constraint(equalTo: view.layoutMarginsGuide.centerXAnchor),
explanationView.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor),
explanationView.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor),
explanationView.bottomAnchor.constraint(greaterThanOrEqualTo: view.layoutMarginsGuide.bottomAnchor, constant: -20)
])
I am using layer.zPosition to display it over a UIImageView. I also add .isScrollEnabled for the text view. I also tried creating the UITextView via a computed property, but did not have any luck either.
Any help is appreciated.
Edit: Below is the code that I use for the text view.
explanationView = UITextView()
explanationView.layer.zPosition = 1
explanationView.translatesAutoresizingMaskIntoConstraints = false
explanationView.isScrollEnabled = true
view.addSubview(explanationView)

Is this new navigation bar behaviour in xcode 11 beta a bug or intended?

I noticed after compiling one of my apps in Xcode 11 beta, that navigation bars have no background when prefersLargeTitles is set. Is this intended behavior?
I noticed this is how the messages app works now when scrolling down and a large title is visible there is no nav bar background.
Here is the code used to set up the navBar attributes:
override func viewWillAppear(_ animated: Bool) {
let textAttributes = [NSAttributedString.Key.foregroundColor:ThemeManager.shared.default1]
self.navigationController?.navigationBar.largeTitleTextAttributes = textAttributes
self.navigationController?.navigationBar.titleTextAttributes = textAttributes
self.navigationController?.navigationBar.tintColor = ThemeManager.shared.default1
self.navigationController?.setNavigationBarHidden(false, animated: true)
self.navigationController?.navigationBar.prefersLargeTitles = true
let nav = self.navigationItem
nav.title = "My Profile"
}
Here are a couple of images showing the difference:
left, compiled on Xcode 10, right, Xcode 11 beta:
Once you scroll up on the 11 Beta version, the background fades back in. Note that apps that are not compiled in Xcode 11 beta will still behave in the normal way, only changes after compiling for some reason. Is this intended, and how would I bring back the original behavior?
This is intended behavior for iOS 13.
Apple's idea (terrible in my opinion) is that the title should merge with the content to show that it is related. Once you start scrolling, when content goes behind the title bar then the title bar will take the "correct" appearance.
The reason this is terrible is because everyone has currently planned all of their UI without this behavior. So the new behavior should be opt-in instead of forcing everyone to opt-out (i.e. the change breaks everyone's code and if you're going to break everyone's code at least you should be clear about how to keep the tried and true behavior of the last 10 years).
As in your case, the result looks horrible. The result looks horrible in my case too.
Apple doesn't give answers but says that you should be using
- scrollEdgeAppearance
From UINavigationBar in order to control the appearance of the bar when content is aligned top-of-content to bottom-of-navbar ... in my case this method returns nil though so I'm currently unsure how we're supposed to use this.
This seems to be discussed here as well:
New UINavigationBar appearance in detail pane of UISplitViewController in iOS 13
So the current workaround would seem to be this in your view controller:
- (void)viewDidLoad;
{
[super viewDidLoad];
if (#available(iOS 13,*)){
UINavigationBar *bar =self.navigationController.navigationBar;
bar.scrollEdgeAppearance = bar.standardAppearance;
}
}
It works, but if it's the intended approach, I don't know...
EDIT:
Doing this does seem to block any additional direct customization to the UINavigationBar as has been noted. Possible that adjusting the scrollEdgeAppearance from here is the way to go. Ugly. Ugly. Ugly.
EDIT: Progress... this is working now for managing the background. You need to call this instead of setting barTint directly.
#interface UINavigationBar (Compatibility)
- (void)setCompatibleTint:(UIColor *)fg andBarTint:(UIColor *)bg;
#end
#implementation UINavigationBar (Compatibility)
- (void)setCompatibleTint:(UIColor *)fg andBarTint:(UIColor *)bg;
{
self.tintColor = fg;
self.barTintColor = bg;
if (#available(iOS 13,*)){
// we need to tell it to adopt old style behavior first
UINavigationBarAppearance *appearance = self.standardAppearance;
appearance.backgroundColor = bg;
NSDictionary *attributes = self.titleTextAttributes;
appearance.titleTextAttributes = attributes;
attributes = self.largeTitleTextAttributes;
appearance.largeTitleTextAttributes = attributes;
self.scrollEdgeAppearance = appearance;
self.standardAppearance = appearance;
self.compactAppearance = appearance;
}
}
#end
I'm not entirely sure yet on the text attributes but it seems to flow from the background color. It's a complete PITA.
It would be nicer to set this as a subclass and override barTint but of course a lot of the UIKit objects create these bars themselves so you won't get the subclass.
Swift version of dbquarrel's solution.
First declare your textAttributes:
let textAttributes = [NSAttributedString.Key.foregroundColor:UIColor.red]
Use these in a UINavigationBarAppearance() to enable you to change the colour of the text in 3 different modes (scollEdge, standard and compact).
override func viewDidLoad() {
super.viewDidLoad()
if #available(iOS 13.0, *) {
let appearance = UINavigationBarAppearance()
appearance.largeTitleTextAttributes = textAttributes
appearance.titleTextAttributes = textAttributes
let bar = self.navigationController?.navigationBar
bar?.scrollEdgeAppearance = appearance
bar?.standardAppearance = appearance
bar?.compactAppearance = appearance
} else {
// Fallback on earlier versions
}
}

How can I animate the change of the navigation bar title

I am looking for a way to run a custom CAAnimation on the UINavigationBar title.
More precisely, I am looking for a way to access the label which displays navigationItem.title and run animations on that.
It is certainly possible to manually create a UILabel and set the navigationBar.titleView accordingly.
This however seems to be too much effort for a hopefully simple problem. Plus, it will not work well with large titles on the UInavigationBar.
The title text is accessible as topItem.text. There is no way to directly access the label which is displaying this text.
So if you want to animate this label, you first have to search for it in the subview of the NavigationBar.
Then, you can apply animations on this label.
See below for an example that fades in the new title from the right.
/// Fades in the new title from the right
///
/// - Parameter newTitle: New title to display on the navigation item
func animateTitle(newTitle: String) {
// Title animation code
let titleAnimation = CATransition()
titleAnimation.duration = 0.25
titleAnimation.type = CATransitionType.push
titleAnimation.subtype = CATransitionSubtype.fromRight
titleAnimation.timingFunction = CAMediaTimingFunction.init(name: CAMediaTimingFunctionName.easeInEaseOut)
// Find the Label which contains the topitem title
if let subviews = navigationController?.navigationBar.subviews {
for navigationItem in subviews {
for itemSubView in navigationItem.subviews {
if let largeLabel = itemSubView as? UILabel {
largeLabel.layer.add(titleAnimation, forKey: "changeTitle")
}
}
}
}
navigationItem.title = newTitle
}