how can I change color of result icon by code in swift?
It seems that the currentImage is nil for clearButton in Swift 4.2 and 4.1.x. It might have been working in the older versions as in many other answers its working for them.
So i created this class with common customizations shown under Usage.
class SearchBar: UISearchBar {
private enum SubviewKey: String {
case searchField, clearButton, cancelButton, placeholderLabel
}
// Button/Icon images
public var clearButtonImage: UIImage?
public var resultsButtonImage: UIImage?
public var searchImage: UIImage?
// Button/Icon colors
public var searchIconColor: UIColor?
public var clearButtonColor: UIColor?
public var cancelButtonColor: UIColor?
public var capabilityButtonColor: UIColor?
// Text
public var textColor: UIColor?
public var placeholderColor: UIColor?
public var cancelTitle: String?
// Cancel button to change the appearance.
public var cancelButton: UIButton? {
guard showsCancelButton else { return nil }
return self.value(forKey: SubviewKey.cancelButton.rawValue) as? UIButton
}
override func layoutSubviews() {
super.layoutSubviews()
if let cancelColor = cancelButtonColor {
self.cancelButton?.setTitleColor(cancelColor, for: .normal)
}
if let cancelTitle = cancelTitle {
self.cancelButton?.setTitle(cancelTitle, for: .normal)
}
guard let textField = self.value(forKey: SubviewKey.searchField.rawValue) as? UITextField else { return }
if let clearButton = textField.value(forKey: SubviewKey.clearButton.rawValue) as? UIButton {
update(button: clearButton, image: clearButtonImage, color: clearButtonColor)
}
if let resultsButton = textField.rightView as? UIButton {
update(button: resultsButton, image: resultsButtonImage, color: capabilityButtonColor)
}
if let searchView = textField.leftView as? UIImageView {
searchView.image = (searchImage ?? searchView.image)?.withRenderingMode(.alwaysTemplate)
if let color = searchIconColor {
searchView.tintColor = color
}
}
if let placeholderLabel = textField.value(forKey: SubviewKey.placeholderLabel.rawValue) as? UILabel,
let color = placeholderColor {
placeholderLabel.textColor = color
}
if let textColor = textColor {
textField.textColor = textColor
}
}
private func update(button: UIButton, image: UIImage?, color: UIColor?) {
let image = (image ?? button.currentImage)?.withRenderingMode(.alwaysTemplate)
button.setImage(image, for: .normal)
button.setImage(image, for: .highlighted)
if let color = color {
button.tintColor = color
}
}
}
Usage:
class ViewController: UIViewController {
#IBOutlet private weak var searchBar: SearchBar!
override func viewDidLoad() {
super.viewDidLoad()
searchBar.clearButtonColor = .purple
searchBar.cancelButtonColor = .magenta
searchBar.searchIconColor = .red
searchBar.placeholderColor = .green
searchBar.textColor = .orange
searchBar.capabilityButtonColor = .green
}
}
Output:
let sb = UISearchBar()
sb.searchBarStyle = UISearchBarStyle.minimal
sb.showsSearchResultsButton = true
// sb.setClearButtonColorTo(color: UIColor.white)
let textFieldInsideSearchBar = sb.value(forKey: "searchField") as? UITextField
let crossIconView = textFieldInsideSearchBar?.value(forKey: "clearButton") as? UIButton
crossIconView?.setImage(crossIconView?.currentImage?.withRenderingMode(.alwaysTemplate), for: .normal)
crossIconView?.tintColor = .white
Related
I have model which contain struct and array . Form Hit struct , I want to access tag property like this tagLabel.text = photoviewModel?.hits.tags, so that I can display the value of the property into label . For example when I click the first collection view cell , it should return image and label properly but problem is I got the correct image but always return same values for tag into label . The screenshot is added into below..
Here is the model .
import Foundation
struct Photo: Codable {
let total, totalHits: Int
let hits: [Hit]
}
struct Hit: Codable {
let id: Int
let pageURL: String
let type, tags: String
let previewURL: String
let previewWidth, previewHeight: Int
let webformatURL: String
let webformatWidth, webformatHeight: Int
let largeImageURL: String
let imageWidth, imageHeight, imageSize, views: Int
let downloads, collections, likes, comments: Int
let userID: Int
let user: String
let userImageURL: String
enum CodingKeys: String, CodingKey {
case id, pageURL, type, tags, previewURL, previewWidth, previewHeight, webformatURL, webformatWidth, webformatHeight, largeImageURL, imageWidth, imageHeight, imageSize, views, downloads, collections, likes, comments
case userID = "user_id"
case user, userImageURL
}
}
Here is the view Model .
import Foundation
class PhotoViewModel {
private let networkManager: NetworkManagerProtocol
var hits = [Hit]()
private var cache = [String: Data]()
// var net = NetworkManager()
weak var delegate: PhotoViewable?
init(networkManager: NetworkManagerProtocol) {
self.networkManager = networkManager
}
var rows: Int {
return hits.count
}
func fecthPhotoRecord(){
let networkUrl = NetworkURLs.baseURL
networkManager.getModel(Photo.self, from: networkUrl) { [weak self] result in
switch result{
case.success(let photo):
self?.hits = photo.hits
self?.delegate?.refreshUI()
case.failure(let error):
print(error)
self?.delegate?.showError()
}
}
}
func downloadImage(row: Int, completion: #escaping (Data) -> Void) {
let hit = hits[row]
let hitpath = hit.previewURL
if let data = cache[hitpath] {
completion(data)
return
}
networkManager
.getData(from: hitpath) { result in
switch result {
case .success(let data):
self.cache[hitpath] = data
DispatchQueue.main.async {
completion(data)
}
case .failure(let error):
print(error)
}
}
}
Here is the code in view controller .
import UIKit
class PhtotoDetailsViewController: UIViewController {
var photoviewModel : PhotoViewModel?
var peopleDetailsViewModel:PeopleDetailsViewModel?
private var stackView: UIStackView = {
let stackView = UIStackView()
stackView.axis = .vertical
stackView.distribution = .fill
stackView.alignment = .fill
stackView.spacing = 5
stackView.translatesAutoresizingMaskIntoConstraints = false
return stackView
}()
private let imageView: UIImageView = {
let imageView = UIImageView(frame: .zero)
imageView.contentMode = .scaleAspectFill
imageView.translatesAutoresizingMaskIntoConstraints = false
return imageView
}()
private let tagLabel: UILabel = {
let label = UILabel(frame: .zero)
label.textAlignment = .center
label.numberOfLines = 0
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
view.addSubview(stackView)
view.addSubview(tagLabel)
view.addSubview(imageView)
setUpUI()
setPhoto()
setContrain()
}
private func setContrain(){
NSLayoutConstraint.activate([
stackView.topAnchor.constraint(equalTo: view.topAnchor,constant: 100),
stackView.bottomAnchor.constraint(equalTo: view.bottomAnchor,constant: -200),
stackView.leftAnchor.constraint(equalTo: view.leftAnchor , constant: 10),
stackView.rightAnchor.constraint(equalTo: view.rightAnchor,constant: 10),
stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
stackView.trailingAnchor.constraint(equalTo: view.trailingAnchor )
])
stackView.addArrangedSubview(imageView)
stackView.addArrangedSubview(tagLabel)
}
private func setUpUI(){
tagLabel.text = photoviewModel?.hits.first?.tags
}
var rowSelected = 0
private func setPhoto(){
photoviewModel?.downloadImage(row: rowSelected) { [weak self] data in
DispatchQueue.main.async {
let image = UIImage(data: data)
self?.imageView.image = image
}
}
}
}
I am having problem on this line ..
private func setUpUI(){
tagLabel.text = photoviewModel?.hits.first?.tags
}
Here is the screenshot when the app load first and it has image and label property ...
When I click the third cell form collection It has the correct image but correct label property .
In your setupUI() replace the line tagLabel.text = photoviewModel?.hits.first?.tags with this
tagLabel.text = photoviewModel?.hits[rowSelected].tags
As you can see in the image I would like to be able to do a similar one, to make a way that instead of showing only the icon of the sun, also showing a text.
As seen in the image below, an icon followed by a text.
But I only managed to do this:
The problem I would like to put the icon on the left or right of the text, not above it, can you give me a hand?
P.s.
The text must change accordingly, how can I make the StatusBarController receive the text changes.
import AppKit
import SwiftUI
class StatusBarController {
#ObservedObject var userPreferences = UserPreferences.instance
private var statusBar: NSStatusBar
private var statusItem: NSStatusItem
private var popover: NSPopover
init(_ popover: NSPopover) {
self.popover = popover
statusBar = NSStatusBar.init()
statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
if let statusBarButton = statusItem.button {
if let _ = userPreferences.$inDownload {
statusItem.button?.title = userPreferences.$percentualDownload
}
statusBarButton.image = #imageLiteral(resourceName: "Weather")
statusBarButton.image?.size = NSSize(width: 18.0, height: 18.0)
statusBarButton.image?.isTemplate = true
statusBarButton.action = #selector(togglePopover(sender:))
statusBarButton.target = self
statusBarButton.imagePosition = NSControl.ImagePosition.imageLeft
}
}
#objc func togglePopover(sender: AnyObject) {
if(popover.isShown) {
hidePopover(sender)
}
else {
showPopover(sender)
}
}
func showPopover(_ sender: AnyObject) {
if let statusBarButton = statusItem.button {
popover.show(relativeTo: statusBarButton.bounds, of: statusBarButton, preferredEdge: NSRectEdge.maxY)
}
}
func hidePopover(_ sender: AnyObject) {
popover.performClose(sender)
}
}
I'm thinking of using something like that:
import EventKit
import ServiceManagement
private struct PreferencesKeys {
static let backgroundIsTransparent = "backgroundIsTransparent"
static let inDownload = "inDownload"
static let percentualDownload = "percentualDownload"
}
class UserPreferences: ObservableObject {
static let instance = UserPreferences()
private init() {
// This prevents others from using the default '()' initializer for this class.
}
private static let defaults = UserDefaults.standard
#Published var backgroundIsTransparent: Bool = {
guard UserDefaults.standard.object(forKey: PreferencesKeys.backgroundIsTransparent) != nil else {
return true
}
return UserDefaults.standard.bool(forKey: PreferencesKeys.backgroundIsTransparent)
}() {
didSet {
UserPreferences.defaults.set(backgroundIsTransparent, forKey: PreferencesKeys.backgroundIsTransparent)
}
}
#Published var inDownload: Bool = {
guard UserDefaults.standard.object(forKey: PreferencesKeys.inDownload) != nil else {
return true
}
return UserDefaults.standard.bool(forKey: PreferencesKeys.inDownload)
}() {
didSet {
UserPreferences.defaults.set(inDownload, forKey: PreferencesKeys.inDownload)
}
}
#Published var percentualDownload: String = {
guard UserDefaults.standard.object(forKey: PreferencesKeys.percentualDownload) != nil else {
return "0%"
}
return UserDefaults.standard.string(forKey: PreferencesKeys.percentualDownload)!
}() {
didSet {
UserPreferences.defaults.set(percentualDownload, forKey: PreferencesKeys.percentualDownload)
}
}
}
but I get the following error:
Edit:
First problem solved I used:
statusBarButton.imagePosition = NSControl.ImagePosition.imageLeft
statusBarButton.imagePosition = NSControl.ImagePosition.imageRight
For the update text problem, what can I do?
Currently this app has a textfield and when you press return on the keyboard it displays the live data on the screen but It deletes the previous question. My question is how can I not delete the previous question and just add this new question to the list. Thank you so much!
import UIKit
import FirebaseFirestore
class ViewController: UIViewController, UITextFieldDelegate {
let database = Firestore.firestore()
private let label: UILabel = {
let label = UILabel()
label.textAlignment = .center
label.numberOfLines = 0
return label
}()
private let field: UITextField = {
let field = UITextField()
field.placeholder = "enter text.."
field.layer.borderWidth = 1
field.layer.borderColor = UIColor.black.cgColor
return field
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(label)
view.addSubview(field)
field.delegate = self
let docRef = database.document("ios/ex")
docRef.addSnapshotListener {[weak self] snapshot, error in
guard let data = snapshot?.data(), error == nil else {
return
}
guard let text = data["text"] as? String else {
return
}
DispatchQueue.main.async {
self?.label.text = text
}
}
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
field.frame = CGRect(x: 1, y: view.safeAreaInsets.top+10, width: view.frame.size.width-20, height: 50)
label.frame = CGRect(x: 1, y: view.safeAreaInsets.top+10+60, width: view.frame.size.width-20, height: 100)
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
if let text = textField.text, !text.isEmpty {
savedData(text: text)
}
return true
}
func savedData(text: String){
let docRef = database.document("ios/ex")
docRef.setData(["text": text])
}
}
save array in your "text" key instead of just string, and update array using this Firebase method called FieldValue.arrayUnion('your new value')
I've been searching all day but couldn't find out a fix for this code.
This extension replaces the UISearchField magnifying icon with UIActivityView (shows the loading icon when is true)
The extension was working fine on iOS 12, Xcode 10.3 but after I've changed into iOS 13, Xcode 11 Beta 4 it stopped working.
I've still made a workaround using this:
if let textFieldInsideSearchBar = searchBar.value(forKey: "searchField") as? UITextField {
let loadingIcon = UIActivityIndicatorView()
loadingIcon.style = .medium
loadingIcon.backgroundColor = UIColor.clear
loadingIcon.startAnimating()
textFieldInsideSearchBar.leftView = loadingIcon
}
But I can't understand the reason why the extension stopped working.
Also I've noticed that .flatMap was deprecated in iOS 13 and changed to .compactMap but as I understood there were no differences, and I've already tried to change the .flatMap to .compactMap but still didn't work.
Here is the extension:
extension UISearchBar {
private var textField: UITextField? {
let subViews = self.subviews.compactMap { $0.subviews }
return (subViews.filter { $0 is UITextField }).first as? UITextField
}
private var searchIcon: UIImage? {
let subViews = subviews.flatMap { $0.subviews }
return ((subViews.filter { $0 is UIImageView }).first as? UIImageView)?.image
}
private var activityIndicator: UIActivityIndicatorView? {
return textField?.leftView?.subviews.compactMap{ $0 as? UIActivityIndicatorView }.first
}
var isLoading: Bool {
get {
return activityIndicator != nil
} set {
let _searchIcon = searchIcon
if newValue {
if activityIndicator == nil {
let _activityIndicator = UIActivityIndicatorView()
_activityIndicator.style = .medium
_activityIndicator.startAnimating()
_activityIndicator.backgroundColor = UIColor.clear
self.setImage(UIImage(), for: .search, state: .normal)
textField?.leftView?.addSubview(_activityIndicator)
let leftViewSize = textField?.leftView?.frame.size ?? CGSize.zero
_activityIndicator.center = CGPoint(x: leftViewSize.width/2, y: leftViewSize.height/2)
}
} else {
self.setImage(_searchIcon, for: .search, state: .normal)
activityIndicator?.removeFromSuperview()
}
}
}
}
There have been some changes with iOS 13 in terms of UISearchBar, And you can use UISearchBar.searchTextField instead of searchBar.value(forKey: "searchField")
searchBar.searchTextField.backgroundColor = .red
Or if you want to keep it work with the extension, You can do this:
var searchTextField: UITextField? {
let subViews = self.subviews.first?.subviews.last?.subviews
return subViews?.first as? UITextField
}
I've been trying to change font in native UIDatePicker in iOS and I did it, but with some unsettled details:
I use extension for UIDatePicker to change font in it's labels:
extension UIDatePicker {
func stylizeView(view: UIView? = nil) {
let view = view ?? self
for subview in view.subviews {
if let label = subview as? UILabel {
if let text = label.text {
print("UIDatePicker :: sylizeLabel :: \(text)\n")
label.font = UIFont(name: "MyriadPro-Light", size: 17)!
}
} else { stylizeView(subview) }
}
}}
So, you can customize font deeply:
struct DatePickerStyle {
let tintColor = UIColor(hex: 0xFFFFFF)
let font = UIFont(name: "MyriadPro-Light", size: 17)!
let fontColor = UIColor(hex: 0x000000)
let fontKern: CGFloat = 0.2
var paragraphStyle: NSMutableParagraphStyle {
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineSpacing = 4
paragraphStyle.lineHeightMultiple = 1
paragraphStyle.alignment = .Right
return paragraphStyle
}}
extension UIDatePicker {
func stylizeView(view: UIView? = nil) {
let style = DatePickerStyle()
let view = view ?? self
for subview in view.subviews {
if let label = subview as? UILabel {
if let text = label.text {
print("UIDatePicker :: sylizeLabel :: \(text)\n")
let attributedString = NSMutableAttributedString(string: text)
let attributedStringRange = NSMakeRange(0, attributedString.length)
attributedString.addAttributes([
NSParagraphStyleAttributeName: style.paragraphStyle,
NSFontAttributeName: style.font,
NSForegroundColorAttributeName: style.fontColor,
NSKernAttributeName: style.fontKern
], range: attributedStringRange)
//label.font = style.font
label.tintColor = style.fontColor
label.attributedText = attributedString
}
} else { stylizeView(subview) }
}
}
}
This function in extension is implemented on any Control Events of UIDatePicker:
datePicker.addTarget(self, action: #selector(CellWithDatePicker.updateDatePickerStyle), forControlEvents: .AllEvents)
&
func updateDatePickerStyle() {
print(":: updateDatePickerStyle")
datePicker.stylizeView()
}
Problem 1:
When I init UIDatePicker, font of the picker is still SanFrancisco.
But when I change value in UIDatePicker the font is changed to my font
My Font
I tried to implement datePicker.stylizeView() or self.stylizeView() on every stage of UIDatePicker lifecycle, but it can only change selected line of DatePicker.
Problem 2:
While I rotating DatePicker after pic.2 when all label of DatePicker is set with newFont, new labels which is outside the selected line is still with old font (SanFrancisco). And when I stop rotating DatePicker all label is updated to newFont.
enter image description here
Any idea how to fix it?
Use GCD with an interval of 0.1 to call the styling function.
Example:
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1)
{
self.datePicker.stylizeView();ss
}
The only way for changing the font of UIDatePickerView (until now) is swizzling:
you can change the font by an extension of UILabel! (this is not recommended but it works!)
import Foundation
import UIKit
public extension UILabel {
#objc func setFontSwizzled(font: UIFont) {
if self.shouldOverride() {
self.setFontSwizzled(font: UIFont.fontWith(style: .regular, size: 14))
} else {
self.setFontSwizzled(font: font)
}
}
private func shouldOverride() -> Bool {
let classes = ["UIDatePicker", "UIDatePickerWeekMonthDayView", "UIDatePickerContentView"]
var view = self.superview
while view != nil {
let className = NSStringFromClass(type(of: view!))
if classes.contains(className) {
return true
}
view = view!.superview
}
return false
}
private static let swizzledSetFontImplementation: Void = {
let instance: UILabel = UILabel()
let aClass: AnyClass! = object_getClass(instance)
let originalMethod = class_getInstanceMethod(aClass, #selector(setter: font))
let swizzledMethod = class_getInstanceMethod(aClass, #selector(setFontSwizzled))
if let originalMethod = originalMethod, let swizzledMethod = swizzledMethod {
// switch implementation..
method_exchangeImplementations(originalMethod, swizzledMethod)
}
}()
static func swizzleSetFont() {
_ = self.swizzledSetFontImplementation
}
}
and for changing the color you just simply call the function below:
datePicker.setValue(UIColor.whiteColor(), forKeyPath: "textColor")
if it's necessary to be re-rendered you need to call:
datePicker.datePickerMode = .CountDownTimer
datePicker.datePickerMode = .DateAndTime //or whatever your original mode was