UISearchBar override clear with refresh and stop similar to safari addressbar - ios

How to customize behavior of UISearchBar clear button to show refresh and stop icons and functionality similar to iOS Safari addressbar. I tried to get hold of UITextField, but it return nil
extension UISearchBar {
func textFiled() -> UITextField? {
for view in self.subviews as [UIView] {
if let textField = view as? UITextField {
return textField
}
}
return nil
}
}

extension UISearchBar {
var textField: UITextField? {
if let textField:UITextField = self.valueForKey("_searchField") as? UITextField {
return textField
}
return nil
}
}

Related

Is there a way to disable accessibility font scaling for a UITableViewRowAction button?

I need a way to disable font scaling for a UITableViewRowAction button. Is there any way to either just disable font scaling or get access to the UITableViewRowAction button itself so I can change it's font size?
I've already tried accessing the button inside of the editActionsForRowAt function and the didTransition(to state: function inside of the UITableViewCell's class by stepping through the subviews.
override func didTransition(to state: UITableViewCell.StateMask) {
super.didTransition(to: state)
if (state.rawValue & UITableViewCell.StateMask.showingDeleteConfirmation.rawValue) == UITableViewCell.StateMask.showingDeleteConfirmation.rawValue {
let deleteButton: UIView? = subviews.first { (view) -> Bool in
return String(describing: view).contains("Delete")
}
if let deleteButton = deleteButton {
print(deleteButton)
}
}
}
I found a way to access the TableViewRowAction button. If you subclass UITableView and override layoutSubviews then you can use this loop to find the UIButton.
override func layoutSubviews() {
super.layoutSubviews()
for subview in subviews {
if NSStringFromClass(type(of: subview)) == "UISwipeActionPullView" {
for case let button as UIButton in subview.subviews {
button.titleLabel?.font = UIFont.systemFont(ofSize: 17)
}
}
}
}

Unable to change background color of TF in search bar implemented using UISearchController

How to change the background color of only textfield? (which is red in the below image)
If is set the background colour to white using the code below then there is not enough top padding
searchController.searchBar.backgroundColor = UIColor.white
Please try this for change the background color of textfield in search bar.
We have many ways to do that:
First way:
extension UISearchBar {
private func getViewElement<T>(type: T.Type) -> T? {
let svs = subviews.flatMap { $0.subviews }
guard let element = (svs.filter { $0 is T }).first as? T else { return nil }
return element
}
func setTextFieldColor(color: UIColor) {
if let textField = getViewElement(type: UITextField.self) {
switch searchBarStyle {
case .minimal:
textField.layer.backgroundColor = color.cgColor
textField.layer.cornerRadius = 6
case .prominent, .default:
textField.backgroundColor = color
}
}
}
}
How to use
searchBar.setTextFieldColor(color: UIColor.green.withAlphaComponent(0.3))
Second way: You can get textfield from searchbar like that and change the color directly:
In Swift
for subView: UIView? in searchBar.subviews {
for field: Any? in subView?.subviews ?? [Any?]() {
if (field is UITextField) {
var textField = field as? UITextField
textField?.backgroundColor = UIColor.gray
}
}
}
In Obj-c
for (UIView *subView in _searchBar.subviews) {
for(id field in subView.subviews){
if ([field isKindOfClass:[UITextField class]]) {
UITextField *textField = (UITextField *)field;
[textField setBackgroundColor:[UIColor grayColor]];
}
}
}
first you need below search bar extension:
extension UISearchBar {
/// Return text field inside a search bar
var textField: UITextField? {
guard let text = self.value(forKey: "searchField") as? UITextField else {
return nil
}
return text
}
}
then at your view controller:
searchBar.textField?.backgroundColor = .red
In Swift :
var textField = searchBar.valueForKey("searchField") as? UITextField
textField?.backgroundColor = UIColor.red // Change as per your requirement

How to get all the textfields from a view in swift

I have a view which has more than 15 UITextFields. I have to set bottomBorder(extension) for all the UITextFields. I can set it one by one for all the UITextFields and its working too. I want to set the bottom border for all the UITextFields at once. Here is the code I am trying but it seems like that for loop is not executing. I have even tried it in viewDidLayoutSubViews but for loop not executing there too.
override func viewDidLoad()
{
super.viewDidLoad()
/** setting bottom border of textfield**/
for case let textField as UITextField in self.view.subviews {
textField.setBottomBorder()
}
}
Swift: This function will return all text-fields in a view. No matter if field exists in any subview. ;-)
func getAllTextFields(fromView view: UIView)-> [UITextField] {
return view.subviews.flatMap { (view) -> [UITextField] in
if view is UITextField {
return [(view as! UITextField)]
} else {
return getAllTextFields(fromView: view)
}
}.flatMap({$0})
}
Usage:
getAllTextFields(fromView : self.view).forEach{($0.text = "Hey dude!")}
Generic Way:
func getAllSubviews<T: UIView>(fromView view: UIView)-> [T] {
return view.subviews.map { (view) -> [T] in
if let view = view as? T {
return [view]
} else {
return getAllSubviews(fromView: view)
}
}.flatMap({$0})
}
Usage:
let textFields: [UITextField] = getAllSubviews(fromView: self.view)
I made it working, but still need the explanation why the code in question is not working
I got it from somewhere on the forum, not exactle able to credit the answer.
/** extract all the textfield from view **/
func getTextfield(view: UIView) -> [UITextField] {
var results = [UITextField]()
for subview in view.subviews as [UIView] {
if let textField = subview as? UITextField {
results += [textField]
} else {
results += getTextfield(view: subview)
}
}
return results
Call the above function in viewDidLoad or viewDidLayoutSubviews.
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
/** setting bottom border to the textfield **/
let allTextField = getTextfield(view: self.view)
for txtField in allTextField
{
txtField.setBottomBorder()
}
}
extension:
extension UIView {
func viewOfType<T:UIView>(type:T.Type, process: (_ view:T) -> Void)
{
if let view = self as? T
{
process(view)
}
else {
for subView in subviews
{
subView.viewOfType(type:type, process:process)
}
}
}
}
Usage:
view.viewOfType(type:UITextField.self) {
view in
view.text = "123"
}
try this
for aSubView: Any in self.view.subviews {
if (aSubView is UITextField) {
var textField = (aSubView as! UITextField)
textField. setBottomBorder()
}
}
or try this
for view in self.view.subviews {
if (view is UITextField) {
var textField = view as! UITextField
textField. setBottomBorder()
}
}
Try this :)
for view in self.view.subviews as! [UIView] {
if let textField = view as? UITextField {
textField.setBottomBorder()
}
}
This worked for me.
var textFieldsArray = [UITextField]()
for view in self.view.subviews {
if view is UITextField {
textFieldsArray.append(view as! UITextField)
}
}
textFieldsArray.forEach { $0.setBottomBorder() }
If you want to get the result of the function applied in a new array, use map() instead.
func getTextFields() {
for textField in view.subviews where view is UITextField {
(textField as? UITextField).setBottomBorder()
}
}
Swift 5
A Very simple answer you can understand easyly
: - You can handle all kind of Objects like UILable, UITextfields, UIButtons, UIView, UIImages . any kind of objecs etc.
for subviews in self.view.subviews {
if subviews is UITextField
{
//MARK: - if the sub view is UITextField you can handle here
funtextfieldsetting(textfield: subviews as! UITextField)
}
if subviews is UIButton
{
//MARK: - if the sub view is UIButton you can handle here
funbuttonsetting(button: subviews as! UIButton)
}
if subviews is UILabel
{
//MARK: - if the sub view is UILabel you can handle here
//Any thing you can do it with label or textfield etc
}
}

Change UITextField height in UISearchBar - Swift 3

is there a way to change the height of a UISearchBar's textField??
I can access the textField like this and although background color changes, nothing seems to change in terms of frame/size...
I was able to change the searchBar height in IB by setting the constraints.
But the textfield stayed the same (44)...
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
self.mySearchBar.layoutIfNeeded()
self.mySearchBar.layoutSubviews()
self.mySearchBar.backgroundColor = UIColor.blue
for subView in mySearchBar.subviews
{
for subsubView in subView.subviews
{
if let textField = subsubView as? UITextField
{
var currentTextFieldBounds = textField.bounds
textField.borderStyle = UITextBorderStyle.none
currentTextFieldBounds.size.height = self.mySearchBar.bounds.height-10
textField.bounds = currentTextFieldBounds
textField.backgroundColor = UIColor.red
}
}
}
The UITextField within the UISearchBar is not directly accessible. You could create your own UISearchBarsubclass to emulate a regular Search bar. You could completely customize the UI as you see fit with either Interface builder or programatically.
protocol SearchBarEventDelegate {
func searchButtonPressed(searchBar: CustomSearchBar)
func searchBarDidReceiveInput(searchText: String)
func searchBarDidBackspace(searchText: String)
}
class CustomSearchBar: UIView {
var searchTextField: UITextField?
var delegate : SearchBarEventDelegate?
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(searchTextField())
}
func searchTextField() -> UITextField {
//Input custom frame and attributes here.
let textField = UITextField(frame: CGRectZero)
textField.delegate = self
return textField
}
}
extension CustomSearchBar : UITextFieldDelegate {
//Implement Textfield delegate methods here.
//Propagate events to CustomSearchBar delegate. Example Provided.
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
let partialSearchString = textField.text!
let fullSearchString = (partialSearchString as NSString).stringByReplacingCharactersInRange(range, withString: string)
if(range.length == 1) {
delegate?.searchBarDidBackspace(fullSearchString)
} else {
delegate?.searchBarDidReceiveInput(fullSearchString)
}
return true
}
}

Using "Next" as a Return Key

I use the "Next" value for the "Return Key" to get the Next button in place of the Done button, but (obviously) pressing it doesn't automatically move to the next UITextField in my view.
What's the right way to do this? I have seen many answers, but anyone have a swift solution?
Make sure your text fields have their delegate set and implement the textFieldShouldReturn method. This is the method that is called when the user taps the return key (no matter what it looks like).
The method might look something like this:
func textFieldShouldReturn(textField: UITextField) -> Bool {
if textField == self.field1 {
self.field2.becomeFirstResponder()
}
return true
}
The actual logic in here might vary. There are numerous approaches, and I'd definitely advise against a massive if/else chain if you have lots of text fields, but the gist here is to determine what view is currently active in order to determine what view should become active. Once you've determined which view should become active, call that view's becomeFirstResponder method.
For some code cleanliness, you might consider a UITextField extension that looks something like this:
private var kAssociationKeyNextField: UInt8 = 0
extension UITextField {
var nextField: UITextField? {
get {
return objc_getAssociatedObject(self, &kAssociationKeyNextField) as? UITextField
}
set(newField) {
objc_setAssociatedObject(self, &kAssociationKeyNextField, newField, .OBJC_ASSOCIATION_RETAIN)
}
}
}
And then change our textFieldShouldReturn method to look like this:
func textFieldShouldReturn(textField: UITextField) -> Bool {
textField.nextField?.becomeFirstResponder()
return true
}
Once you've done this, it should simply be a matter of setting each text field's new nextField property in viewDidLoad:
self.field1.nextField = self.field2
self.field2.nextField = self.field3
self.field3.nextField = self.field4
self.field4.nextField = self.field1
Although if we really wanted, we could prefix the property with #IBOutlet, and that would allow us to hook up our "nextField" property right in interface builder.
Change the extension to look like this:
private var kAssociationKeyNextField: UInt8 = 0
extension UITextField {
#IBOutlet var nextField: UITextField? {
get {
return objc_getAssociatedObject(self, &kAssociationKeyNextField) as? UITextField
}
set(newField) {
objc_setAssociatedObject(self, &kAssociationKeyNextField, newField, .OBJC_ASSOCIATION_RETAIN)
}
}
}
And now hook up the nextField property in interface builder:
(Set up your delegate while you're here too.)
And of course, if the nextField property returns nil, the keyboard just hides.
Here is an example in Swift:
I created a screen with 6 UITextFields. I assigned them the tags 1 through 6 in Interface Builder. I also changed the Return key to Next in IB. Then I implemented the following:
import UIKit
// Make your ViewController a UITextFieldDelegate
class ViewController: UIViewController, UITextFieldDelegate {
// Use a dictionary to define text field order 1 goes to 2, 2 goes to 3, etc.
let nextField = [1:2, 2:3, 3:4, 4:5, 5:6, 6:1]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
// Make ourselves the delegate of the text fields so that textFieldShouldReturn
// will be called when the user hits the Next/Return key
for i in 1...6 {
if let textField = self.view.viewWithTag(i) as? UITextField {
textField.delegate = self
}
}
}
// This is called when the user hits the Next/Return key
func textFieldShouldReturn(textField: UITextField) -> Bool {
// Consult our dictionary to find the next field
if let nextTag = nextField[textField.tag] {
if let nextResponder = textField.superview?.viewWithTag(nextTag) {
// Have the next field become the first responder
nextResponder.becomeFirstResponder()
}
}
// Return false here to avoid Next/Return key doing anything
return false
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
There is nothing wrong with the other answers, this is just a different approach with the benefit of being more focused on OOP - imho (although this is a bit more work up front, it can be reused). In the storyboard, I start off adding tags with a distinct range (e.g 800-810) that define the specific order of the fields I want to move between. This has the benefit of working across all subviews in the main view so that one can navigate between UITextField's and UITextView's (and any other control) as needed.
Generally - I typically try to have view controllers message between views and custom event handler objects. So I use a message (aka, NSNotification) passed back to the view controller from a custom delegate class.
(TextField Delegate Handler)
Note: In AppDelegate.swift: let defaultCenter = NSNotificationCenter.defaultCenter()
//Globally scoped
struct MNGTextFieldEvents {
static let NextButtonTappedForTextField = "MNGTextFieldHandler.NextButtonTappedForTextField"
}
class MNGTextFieldHandler: NSObject, UITextFieldDelegate {
var fields:[UITextField]? = []
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
return true
}
func textFieldDidBeginEditing(textField: UITextField) {
textField.backgroundColor = UIColor.yellowColor()
}
func textFieldDidEndEditing(textField: UITextField) {
textField.backgroundColor = UIColor.whiteColor()
}
func textFieldShouldBeginEditing(textField: UITextField) -> Bool {
return true
}
func textFieldShouldClear(textField: UITextField) -> Bool {
return false
}
func textFieldShouldEndEditing(textField: UITextField) -> Bool {
return true
}
func textFieldShouldReturn(textField: UITextField) -> Bool {
//passes the message and the textField (with tag) calling the method
defaultCenter.postNotification(NSNotification(name: MNGTextFieldEvents.NextButtonTappedForTextField, object: textField))
return false
}
}
This allows my view controller to remain focused on it's main job of handling the messaging between objects, model and view.
(View Controller receives a message from the delegate and passes instructions using the advanceToNextField function)
Note: In my storyboard my custom handler classes are defined using an NSObject and that object is linked into the storyboard as a delegate for the controls that I need monitored. Which causes the custom handler class to be initialized automatically.
class MyViewController: UIViewController {
#IBOutlet weak var tagsField: UITextField! { didSet {
(tagsField.delegate as? MNGTextFieldHandler)!.fields?.append(tagsField)
}
}
#IBOutlet weak var titleField: UITextField!{ didSet {
(titleField.delegate as? MNGTextFieldHandler)!.fields?.append(titleField)
}
}
#IBOutlet weak var textView: UITextView! { didSet {
(textView.delegate as? MNGTextViewHandler)!.fields?.append(textView)
}
}
private struct Constants {
static let SelectorAdvanceToNextField = Selector("advanceToNextField:")
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
registerEventObservers()
}
override func viewDidDisappear(animated: Bool) {
super.viewDidDisappear(animated)
deRegisterEventObservers()
}
func advanceToNextField(notification:NSNotification) {
let currentTag = (notification.object as! UIView).tag
for aView in self.view.subviews {
if aView.tag == currentTag + 1 {
aView.becomeFirstResponder()
}
}
}
func registerEventObservers () {
defaultCenter.addObserver(self, selector: Constants.SelectorAdvanceToNextField, name: MNGTextFieldEvents.NextButtonTappedForTextField, object: nil)
}
func deRegisterEventObservers() {
defaultCenter.removeObserver(self, name: MNGTextFieldEvents.NextButtonTappedForTextField, object: nil)
}
....
}
Just another way to achieve the result that I found helpful. My app had 11 text fields followed by a text view. I needed to be able to cycle through all fields using the next key and then resign the keyboard following the textview (i.e. other notes).
In the storyboard, I set the tag on all of the fields (both text and textview) starting with 1 through 12, 12 being the textview.
I'm sure there are other ways to do it and this method isn't perfect, but hopefully it helps someone.
In code, I wrote the following:
func textFieldShouldReturn(textField: UITextField) -> Bool {
let nextTag = textField.tag + 1
//Handle Textview transition, Textfield programmatically
if textField.tag == 11 {
//Current tag is 11, next field is a textview
self.OtherNotes.becomeFirstResponder()
} else if nextTag > 11 {
//12 is the end, close keyboard
textField.resignFirstResponder()
} else {
//Between 1 and 11 cycle through using next button
let nextResponder = self.view.viewWithTag(nextTag) as? UITextField
nextResponder?.becomeFirstResponder()
}
return false
}
func textFieldDidEndEditing(textField: UITextField) {
textField.resignFirstResponder()
}
func textView(textView: UITextView, shouldChangeTextInRange range: NSRange, replacementText text: String) -> Bool {
//Remove keyboard when clicking Done on keyboard
if(text == "\n") {
textView.resignFirstResponder()
return false
}
return true
}
Another approach, if you're using storyboards, you can change the textfield's attribute for Return Key.
Currently you have the following options: Default (Return), Go, Google, Join, Next, Route, Search, Send, Yahoo, Done, Emergency Call, Continue

Resources