Custom SearchBar iOS - ios

I am trying to create a Custom SearchBar so that I can
Left Align the search image
Change the corners of textbook to be a little more rounded.
After the image is left aligned, the placeholder text can also be left aligned
Change color of the search text
However, even after subclassing UISearchBar I am unable to achieve what I want and facing following problems:
When debugging, I can see UISearchBar in the view & searchField inside it, but upon iteration on self.subViews and checking if the element is UITextField, I don't get anything. Re-checked and I always get one subView which has different memory address than that of searchbar in debug mode
var customSearchBar:UISearchBar?
var searchField:UITextField?
var button:UIButton?
override func layoutSubviews() {
for subView in self.subviews {
if (subView.isKindOfClass(UITextField)) {
searchField = (subView as! UITextField)
break
}
}
if ((searchField) != nil) {
searchField?.textColor = UIColor.redColor() //Testing if code works?
}
super.layoutSubviews()
}
I don't want to use undocumented ways and have almost tried all the ways I could find here, without any success
Kindly help me. I am using Xcode 7 and iOS 8

1.You can try this code:
let searchBar = UISearchBar(frame: CGRect(x: 0, y: 0, width: 300, height: 40))
searchBar.backgroundImage = UIImage()//Without this you can't change background color
searchBar.backgroundColor = UIColor.greenColor()
searchBar.showsCancelButton = true
searchBar.returnKeyType = .Done
searchBar.placeholder = "Test text"
var txtSearchField = searchBar.valueForKey("_searchField")
txtSearchField?.layer.cornerRadius = 15
txtSearchField?.layer.borderWidth = 1.5
txtSearchField?.layer.borderColor = UIColor.orangeColor().CGColor
let image = txtSearchField?.subviews?[1]
image?.frame = CGRect(x: 0, y: 7.5, width: 13, height: 13)
let lbl = txtSearchField?.subviews.last as! UILabel
lbl.textColor = UIColor.whiteColor()
lbl.backgroundColor = UIColor.blueColor()
lbl.frame = CGRect(x: 13, y: 1, width: 127.5, height: 25)
Result from Playground:
Also you can look at this and this open source solutions.

To understand logic of how UISearchBar is build you can have a look on their UISearchBar.h file with all private apis here. As you can see, it has UISearchBarTextField as a subview.
Last year in iOS 7 project I used this code to access UITextField of UISearchBar:
UITextField *searchBarTextField;
for (UIView *subview in self.searchBar.subviews) {
for (UIView *subsubview in subView.subviews){
if ([subsubview isKindOfClass:[UITextField class]]) {
searchBarTextField = (UITextField *)subsubview;
break;
}
}
}
However, I would not recommend to use this logic, as if Apple changes private API method you UI and layout might be broken. You can try to use loop method to loop threw all subviews of subviews of UISearchBar and find needed text field, but again, it might be removed any time.
In the future, if you have any questions on how to access any subview on Apple build UI - you can debugger view hierarchy by pressing this button on debug panel:
If you want to make it custom - why not creating you own one? UIView + UITextField + UIButton + some logic = UISearchBar.

Related

How do I prevent Keyboard from adding its own height constraint when using view as inputAccessoryView

I'm trying to use a custom view as an accessory view over the keyboard, for various reasons, in this case, it is much preferred over manual keyboard aligning because of some other features.
Unfortunately, this is a dynamic view that defines its own height. The constraints all work fine outside of the context of an accessoryView without errors, and properly resizing
When added as a keyboardAccessoryView it seems to impose a height of whatever the frame is at the time and break other height constraints
It appears as:
"<NSLayoutConstraint:0x600003e682d0 '_UIKBAutolayoutHeightConstraint' Turntable.ChatInput:0x7fb629c15050.height == 0 (active)>"
(where 0 would correspond to whatever height had been used at initialization
It is also labeled accessoryHeight which should make it easy to remove, but unfortunately, before I can do this, I'm getting unsatisfiable constraints and the system is tossing my height constraints
Tried:
in the inputAccessoryView override, I tried to check for the constraints and remove it, but it doesn't exist at this time
setting translatesAutoresizing...Constraints = false
tl;dr
Using a view as a KeyboardAccessoryView is adding its own height constraint after the fact, can I remove this?
Looks like keyboard doesn't like inputAccessoryView with height constraint. However you still can have inputAccessoryView with dynamic height by using frame (it is still possible to use constraints inside your custom inputAccessoryView).
Please check this example:
import UIKit
final class ViewController: UIViewController {
private let textField: UITextField = {
let view = UITextField()
view.frame = .init(x: 100, y: 100, width: 200, height: 40)
view.borderStyle = .line
return view
}()
private let customView: UIView = {
let view = UIView()
view.backgroundColor = .red
view.frame.size.height = 100
view.autoresizingMask = .flexibleHeight // without this line height won't change
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(textField)
textField.inputAccessoryView = customView
textField.becomeFirstResponder()
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
self.customView.frame.size.height = 50
self.textField.reloadInputViews()
}
}
}

Applying an extension to only certain instances of UIButton

I have a UIButton extension to make some of my buttons have a round shape:
extension UIButton {
override open var intrinsicContentSize: CGSize {
let originalContentSize = super.intrinsicContentSize
let height = originalContentSize.height + 5
layer.cornerRadius = height / 2
layer.masksToBounds = true
return CGSize(width: originalContentSize.width + 35, height: height)
}
}
This works as intended for the buttons within my views. However, I also have some buttons in my navigation bars, which have been affected by this extension and now appear in their containers with extra padding that is rather unsightly.
This is how I usually create my navigation bar buttons:
private func navBarButton() -> UIBarButtonItem {
let button = UIBarButtonItem(image: UIImage(named: "x"), style: .plain, target: self, action: #selector(x))
button.tintColor = .black
return button
}
Before I added my extension to the UIButton class, my navbar buttons used to look like this:
Now, they look like this:
which I do not want. How do I make my navbar buttons go back to what they looked like before adding my extension?
Change your extension from an override to a static function so it only happens when you explicitly instantiate one:
extension UIButton {
static func createAsRound(size: CGSize) -> UIButton {
let button = UIButton(type: .system)
button.frame = CGRect(origin: CGPoint.zero, size: size)
button.layer.cornerRadius = size.height/2
button.layer.masksToBounds = true
return button
}
}
Usage:
let myButton = UIButton.createAsRound(size: CGSize(width: 40, height: 40))
EDIT. If you are using auto layout:
In your extension, add to 'createAsRound`
button.translatesAutoresizingMaskIntoConstraints = false
Then add another method that will only be called as needed:
func makeRound() {
button.layer.cornerRadius = self.frame.height/2
self.setNeedsDisplay()
}
This edit is untested code. I'll edit as needed. (The first code I've used many times and with auto layout).
Obviously the one line of code to add is, well, actually just a convenience. Once you set the auto mask flag you're telling iOS to use auto layout. Setting a "frame" versus setting explicit origin/size constraints - particularly if using layout anchors can be done in the extension as long as you've already instantiated the button's superview.
Dynamically changing the size? Once you glean the UIViewController hierarchy, it should be simple. One like place is viewDidLayoutSubviews where the instantiated UIButton should have had auto layout compute it's frame... at which point you just make it round.
This may not exactly fit your needs, but it should be a good start to creating exactly a round UIButton only when you want to.
Why not to use an explicit class that will do exactly what you need?
class RoundedButton: UIButton {
open override var intrinsicContentSize: CGSize {
let originalContentSize = self.intrinsicContentSize
let height = originalContentSize.height + 5
layer.cornerRadius = height / 2
layer.masksToBounds = true
return CGSize(width: originalContentSize.width + 35, height: height)
}
}

Swift iOS - Bottom Border on TextField initial wrong width

I implement a solution to put a bottom border to a textfield which works good but i have following Problem. If i start the app on bigger sizes (iPad mini, iPad Pro) or landscape (iPhone6, 6s) the line under the textfield is not correct stretched.
I have create a extension for the textfields:
extension UITextField {
/**
Customize the UITextField for App
- parameter isPasswordField: Boolean to check if this field is a password field
- author: Simon Zwicker <simon.zwicker#gmail.com>
*/
func customize(isPasswordField: Bool) {
let bottomLine = UIView()
bottomLine.frame = CGRect(x: 0.0, y: self.frame.size.height - 1, width: self.frame.size.width, height: 1.0)
bottomLine.backgroundColor = UIColor.grayColor()
self.addSubview(bottomLine)
self.tintColor = UIColor.grayColor()
if isPasswordField {
self.textColor = UIColor.blueColor()
}
}
I call the customize() function on the textfields in viewWillLayoutSubviews()
Did i make somewhere a mistake? After i put device in portrait and back to landscape it works.
The size of the textfield is initial correct but the self.frame.size.width in the customize function is initial too small. Do you know what happened maybe?
I was able to replicate your issue, and it seems like at the moment of calling viewWillLayoutSubviews the text field did not have the width that's shown on the screen in the end (or constraints weren't applied).
Calling customize in viewDidLayoutSubviews scaled the line properly. I am not sure if you are happy with this solution.

Trouble displaying programmatically created views as topmost views?

I have a UIViewController (named Friends) with a tableview and I want to display views above the Controller's view when the user is offline. This is how I create my views
class func showOffline(host: UIViewController) {
// showOffline image
var disconnectedImage = UIImage(named: "offlineIconGrey")
disconnectedImage = disconnectedImage?.imageWithRenderingMode(.AlwaysOriginal)
let imageView = UIImageView(image: disconnectedImage)
imageView.center = CGPoint(x: host.view.center.x,
y: host.view.center.y - imageView.frame.height/2)
// showOffline Label
let Label = UILabel(frame: CGRect(x: 0, y: 0, width: 0, height: 0))
let labelText = "You appear to be offline \n Please connect to the internet"
Label.center = CGPoint(x: host.view.center.x,
y: imageView.center.y + 3*imageView.frame.height/4)
Label.numberOfLines = 0
Label.text = labelText
Label.textAlignment = .Center
Label.font = UIFont(name: (Label.font?.fontName)!, size: 14.0)
Label.sizeToFit()
Label.textColor = UIColor.whiteColor()
Label.lineBreakMode = .ByCharWrapping
Label.frame.offsetInPlace(dx: -Label.frame.size.width/2, dy: 0)
host.view.insertSubview(imageView, aboveSubview: host.view)
host.view.insertSubview(Label, aboveSubview: host.view)
}
Now it is my understanding, that the host view (host.view) should be the topmost view as that is shown on my storyboard; however, when I call my showOffline function like so
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
if !Reachability.isConnected() {
state = .Offline
} else {
state = .Default
}
}
where offline state calls,
General.showOffline(self)
like so
var state: State = .Default {
didSet {
switch (state) {
case .Default:
print("ViewMode = Default")
//query = ParseHelper.allUsers(updateList)
case .Search:
let searchText = searchBar?.text ?? ""
print(searchText)
//query = ParseHelper.searchUsers(searchText, completionBlock:updateList)
case .Offline:
General.showOffline(self)
searchBar.userInteractionEnabled = false
query = nil
users = [PFUser]() ?? []
print("ViewMode = Offline")
}
}
}
the views do not appear as the topmost views unless I am returning to the view controller (Friends) from a show segue. Otherwise, navigating to that view controller does not show the labels when it should? Am I missing something? I have tried to fix the problem by placing the views above the tableview but in that case the math for centering the views is off, probably because I am not loading any cells and I have a footer view as a UIView. I also tried adding the views to the tableview's footer view, but that didn't work either. What I DON'T want is to add the views to the navigation controller's view as I do not want that behavior, though I will do that if I have no other option.
This is what I want
Can someone please tell me what I am doing wrong or refer me to information that may help me fix my problem? Thanks!
host.view.insertSubview(imageView, aboveSubview: host.view)
This cannot work. You add a subview to host.view. The "aboveSubview" must be a subview of host.view. There are other methods for g
adding a subview.
When you call insertSubview:aboveSubview:, the view you give as second parameter must be a subview of the view you're inserting in. Here, it's the same view, so obviously it won't work.
Unless there are other views that should remain above the ones you're inserting, the easiest way is to just use addSubview:, which will make the new view topmost among the child of the target view.

How to display UIView over keyboard in iOS

I want to create a simple view over keyboard, when users tap "Attach" button in inputAccessoryView.
Something like this:
Is there an easy way to do it? Or i should create my custom keyboard?
You can add that new subview to your application window.
func attach(sender : UIButton)
{
// Calculate and replace the frame according to your keyboard frame
var customView = UIView(frame: CGRect(x: 0, y: self.view.frame.size.height-300, width: self.view.frame.size.width, height: 300))
customView.backgroundColor = UIColor.redColor()
customView.layer.zPosition = CGFloat(MAXFLOAT)
var windowCount = UIApplication.sharedApplication().windows.count
UIApplication.sharedApplication().windows[windowCount-1].addSubview(customView);
}
Swift 4 version:
let customView = UIView(frame: CGRect(x: 0, y: self.view.frame.size.height - 300, width: self.view.frame.size.width, height: 300))
customView.backgroundColor = UIColor.red
customView.layer.zPosition = CGFloat(Float.greatestFiniteMagnitude)
UIApplication.shared.windows.last?.addSubview(customView)
The trick is to add the customView as a top subview to the UIWindow that holds the keyboard - and it happens to be the last window in UIApplication.shared.windows.
Swift 4.0
let customView = UIView(frame: CGRect(x: 0, y: self.view.frame.size.height-300, width: self.view.frame.size.width, height: 300))
customView.backgroundColor = UIColor.red
customView.layer.zPosition = CGFloat(MAXFLOAT)
let windowCount = UIApplication.shared.windows.count
UIApplication.shared.windows[windowCount-1].addSubview(customView)
As Tamás Sengel said, Apple's guidelines does not support adding a view over the keyboard. The recommended way to add a view over keyboard in Swift 4 & 5 is:
1) Add view with your "Next" button in your storyboard as external view and connect in your class (see Explain Image), in my case:
IBOutlet private weak var toolBar: UIView!
2) For the textfield you want to add your custom view over keyboard, add it as accessory view in viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
phoneNumberTextField.inputAccessoryView = toolBar
}
3) Add action for "Next" button:
#IBAction func nextButtonPressed(_ sender: Any) {
descriptionTextView.becomeFirstResponder()
// or -> phoneNumberTextField.resignFirstResponder()
}
Explain Image:
Method 2: Result with image
In TableView Controller - add stricked view at bottom
Please follow this great link to handle safe area for screens like iPhone X if you want to use this method(2). Article: InputAccessoryView and iPhone X
override var inputAccessoryView: UIView? {
return toolBar
}
override var canBecomeFirstResponder: Bool {
return true
}
Do you have find some effective method to solve this problem? In iOS9,you put your customView on the top of the windows:
UIApplication.sharedApplication().windows[windowCount-1].addSubview(customView);
But if the keyboard dismisses, the top Windows will be removed, so your customView will be removed.
Looking forward for your help!
Thank you for your help!
You can definitely add the view to your application’s window, and you can also add another window entirely. You can set its frame and level. The level could be UIWindowLevelAlert.
While this can be possible with accessing the topmost window, I would avoid doing this, as it clearly interferes with Apple's guidelines.
What I would do is dismissing the keyboard and replacing its frame with a view with same dimensions.
The keyboard's frame can be accessed from keyboard notifications listed here, their userInfo contain a key that can be accessed with UIKeyboardFrameEndUserInfoKey.

Resources