I am having trouble with two of my pickerViews. I am wondering if it is possible to change the global text color of my pickerViews in the AppDelegate file?
I know some stuff can be done like:
UIPickerView.appearance().backgroundColor = color6
UIPickerView.appearance().tintColor = color4
If I need to write it in my code manually in each pickerView. What sort of pickerView should it be coded into, titleForRow?
Try to use the following class. I have subclassed the UIPickerView and specified colours inside. This is working fine. when you set the items. it will set delegate and datasource to self and will do the job for you.
import UIKit
class CustomUIPickerView: UIPickerView{
let textColor: UIColor = .white
let backGroundColor: UIColor = .blue
let _tintColor: UIColor = .white
var items: [String]?{
didSet{
self.delegate = self
self.dataSource = self
self.reloadAllComponents()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
func commonInit(){
self.backgroundColor = backGroundColor
self.tintColor = _tintColor
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
}
extension CustomUIPickerView: UIPickerViewDataSource{
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return self.items?.count ?? 0
}
}
extension CustomUIPickerView: UIPickerViewDelegate{
func pickerView(_ pickerView: UIPickerView, attributedTitleForRow row: Int, forComponent component: Int) -> NSAttributedString? {
let string = self.items?[row] ?? ""
return NSAttributedString(string: string, attributes: [NSAttributedStringKey.foregroundColor: textColor])
}
}
However if you want to handle the delegate and datasource in your view controller then you can take the advantage of using Extensions here is how:
extension String{
func stringForPickerView() -> NSAttributedString{
let color: UIColor = .white
return NSAttributedString(string: self, attributes: [NSAttributedStringKey.foregroundColor: color])
}
}
then in your ViewController:
extension ViewController: UIPickerViewDelegate{
func pickerView(_ pickerView: UIPickerView, attributedTitleForRow row: Int, forComponent component: Int) -> NSAttributedString? {
return self.items[row].stringForPickerView() // from Extension
}
}
Related
I am trying to make a WYSIWYG editor by interfacing between SwiftUI and UIKit via a UIViewRepresentable.
I am not sure how to change the background colour/image of a UIBarButtonItem to show that the underline attribute button is selected. The UIBarButtonItem is inside of a UIToolbar, which is inside of a UITextView. I need the underline button to be a different colour when either the selected text contains the underline attribute, or if the typingAttributes contains the underline attribute (because the underline button has been selected).
Any help would be greatly appreciated.
Below is the code:
import SwiftUI
import UIKit
struct ContentView: View {
#State private var mutableString: NSMutableAttributedString = NSMutableAttributedString(
string: "this is the NSMutableAttributeString with no attributes")
var body: some View {
WYSIWYG(outerMutableString: $mutableString)
}
}
struct WYSIWYG: UIViewRepresentable {
#Binding var outerMutableString: NSMutableAttributedString
func makeCoordinator() -> Coordinator {
Coordinator()
}
func makeUIView(context: Context) -> UITextView {
context.coordinator.textView
}
func updateUIView(_ uiView: UITextView, context: Context) {
uiView.attributedText = outerMutableString
context.coordinator.stringDidChange = { string in
outerMutableString = string
}
}
class Coordinator: NSObject, UITextViewDelegate {
private let fontSize: CGFloat = 32.0
// var to check if the underline button has been pressed
private var underlineIsSelected: Bool = false
lazy var textView: UITextView = {
let textView = UITextView()
textView.font = UIFont(name: "Helvetica", size: fontSize)
textView.delegate = self
// make toolbar
let toolBar = UIToolbar(frame: CGRect(x: 0, y: 0, width: textView.frame.size.width, height: 44))
// make toolbar underline button
let underlineButton = UIBarButtonItem(
image: UIImage(systemName: "underline"),
style: .plain,
target: self,
action: #selector(underline))
toolBar.items = [underlineButton]
toolBar.sizeToFit()
textView.inputAccessoryView = toolBar
return textView
}()
var stringDidChange: ((NSMutableAttributedString) -> ())?
func textViewDidChange(_ textView: UITextView) {
stringDidChange?(textView.textStorage)
}
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
var list: [NSAttributedString.Key: Any] = [:]
if underlineIsSelected { list[.underlineStyle] = NSUnderlineStyle.single.rawValue }
textView.typingAttributes = list
stringDidChange?(textView.textStorage)
return true
}
func textViewDidChangeSelection(_ textView: UITextView) { }
#objc func underline() {
let range = textView.selectedRange
if (range.length > 0) {
if (isActive(key: .underlineStyle)) {
textView.textStorage.removeAttribute(
.underlineStyle,
range: range)
} else {
textView.textStorage.addAttribute(
.underlineStyle,
value: NSUnderlineStyle.single.rawValue,
range: range)
}
stringDidChange?(textView.textStorage)
}
underlineIsSelected.toggle()
}
// func to check if the selected part of the NSMutableAttributedString contains the attribute key
func isActive(key: NSAttributedString.Key) -> Bool {
var range = textView.selectedRange
if range.length > 0 {
return (textView.textStorage.attribute(
key,
at: range.location,
longestEffectiveRange: &range,
in: range) != nil) ? true : false
}
return false
}
}
}
I'm trying to implement an NSTableView in a SwiftUI app in MacOS using NSViewRepresentable. The part of creating the table view works well so far:
struct TableView: NSViewRepresentable {
typealias NSViewType = NSTableView
func makeNSView(context: Context) -> NSTableView {
let tableView = NSTableView(frame: .zero)
tableView.delegate = context.coordinator
tableView.dataSource = context.coordinator
tableView.rowHeight = 20.0
tableView.gridStyleMask = [.solidVerticalGridLineMask, .solidHorizontalGridLineMask]
let nameColumn = NSTableColumn(identifier: NSUserInterfaceItemIdentifier(rawValue: "Name"))
nameColumn.title = "Name"
tableView.addTableColumn(nameColumn)
let ageColumn = NSTableColumn(identifier: NSUserInterfaceItemIdentifier(rawValue: "Age"))
ageColumn.title = "Age"
tableView.addTableColumn(ageColumn)
return tableView
}
func updateNSView(_ nsView: NSTableView, context: Context) {
nsView.reloadData()
}
func makeCoordinator() -> Coordinator {
return Coordinator()
}
}
extension TableView {
final class Coordinator: NSObject, NSTableViewDelegate, NSTableViewDataSource {
func numberOfRows(in tableView: NSTableView) -> Int {
return 12
}
func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? {
switch tableColumn?.identifier.rawValue {
case "Name":
return "Person \(row)"
case "Age":
return "\(25 + row)"
default:
return nil
}
}
func tableView(_ tableView: NSTableView, shouldEdit tableColumn: NSTableColumn?, row: Int) -> Bool {
return true
}
func tableView(_ tableView: NSTableView, setObjectValue object: Any?, for tableColumn: NSTableColumn?, row: Int) {
}
}
}
Of course, this only gives me the pure table, without a NSScrollView or a NSHeaderView.
My question is now: which would be the most elegant and SwiftUI-ish way to integrate NSScrollView and NSHeaderView?
bundle all three with NSViewRepresentable (very monolithic)
pull the NSHeaderView from my NSViewRepresentable and somehow set it up inside SwiftUI's ScrollView (not sure how I would achieve that it only scrolls horizontally though)
something else?
Thanks!
Thanks for you question, I used your code as base for my solution.
Basically to show the NSTable header you need to wrap the NSTableView in a ScrollView.
This is my final code
struct TableView: NSViewRepresentable {
typealias NSViewType = NSScrollView
func makeNSView(context: Context) -> NSScrollView {
let tableContainer = NSScrollView(frame: .zero)
let tableView = NSTableView(frame: .zero)
tableView.delegate = context.coordinator
tableView.dataSource = context.coordinator
tableView.rowHeight = 20.0
tableView.gridStyleMask = [.solidVerticalGridLineMask, .solidHorizontalGridLineMask]
let nameColumn = NSTableColumn(identifier: NSUserInterfaceItemIdentifier(rawValue: "Name"))
nameColumn.title = "Name"
tableView.addTableColumn(nameColumn)
let ageColumn = NSTableColumn(identifier: NSUserInterfaceItemIdentifier(rawValue: "Age"))
ageColumn.title = "Age"
tableView.addTableColumn(ageColumn)
tableContainer.documentView = tableView
tableContainer.hasVerticalScroller = true
return tableContainer
}
func updateNSView(_ nsView: NSScrollView, context: Context) {
guard let tableView = nsView.documentView as? NSTableView else { return }
tableView.reloadData()
}
func makeCoordinator() -> Coordinator {
return Coordinator()
}
}
No matter what I try, I cannot control the width of a custom picker view integrated into Swift UI with UIViewRepresentable.
Here is the ContentView:
struct ContentView: View {
#State private var pickerData:[[String]] =
[
["silhouette_1" , "silhouette_2" , "silhouette_3" ],
["silhouette_2" , "silhouette_3" , "silhouette_1" ],
["silhouette_3" , "silhouette_1" , "silhouette_2" ]
]
var body: some View {
VStack{
SilhouettePickerView(pickerData: $pickerData)
}
}
}
And here is the SilhouettePickerView:
struct SilhouettePickerView: UIViewRepresentable {
#Binding var pickerData : [[String]]
func makeCoordinator() -> SilhouettePickerView.Coordinator {
return SilhouettePickerView.Coordinator(self)
}
func makeUIView(context: UIViewRepresentableContext<SilhouettePickerView>) -> UIPickerView {
let picker = UIPickerView(frame: .zero)
//let picker = UIPickerView(frame: CGRect(x: 0, y: 0, width: ????, height: ?????))
picker.dataSource = context.coordinator
picker.delegate = context.coordinator
return picker
}
func updateUIView(_ view: UIPickerView, context: UIViewRepresentableContext<SilhouettePickerView>) {
}
class Coordinator: NSObject, UIPickerViewDataSource, UIPickerViewDelegate {
var parent: SilhouettePickerView
init(_ pickerView: SilhouettePickerView) {
self.parent = pickerView
}
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return parent.pickerData.count
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return parent.pickerData[component].count
}
func pickerView(_ pickerView: UIPickerView, widthForComponent component: Int) -> CGFloat {
//return UIScreen.main.bounds.width/3
pickerView.bounds.width/3 - 8
}
func pickerView(_ pickerView: UIPickerView, rowHeightForComponent component: Int) -> CGFloat {
return 126
}
func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView {
let view = UIView(frame: CGRect(x: 0, y: 0, width:pickerView.bounds.width, height: 0))
let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 90, height: 126))
for _ in 0..<self.parent.pickerData.count {
imageView.image = UIImage(named: self.parent.pickerData[component][row])
}
view.addSubview(imageView)
return view
}
}
}
This is the result.
I want it to fill up more of the screen. Also, I want the component rows centered; you'll notice the gray at the right edge. (Anyway to get rid of that?)
I've played around with setting the frame size in makeUIView:
let picker = UIPickerView(frame: CGRect(x: 0, y: 0, width: 400, height: 200))
But nothing changes.
Any suggestions? Thanks!
I'm trying to create a framework that does an API call and creates a PickerView with the populated data from the API call. The iOS client will import this framework and call that one exposed function that should return the PickerView with the loaded data in it.
I managed to create a function that creates the PickerView but can't figure out how to insert the data inside the PickerView.
// Framework Side
public static func returnPickerView() -> UIPickerView {
let apple = ["apple", "orange", "durian", "banana"]
let customPicker = UIPickerView()
customPicker.frame = CGRect(x: 0, y: 0, width: 300, height: 300)
customPicker.layer.borderColor = UIColor.black.cgColor
customPicker.layer.borderWidth = 1
return customPicker
}
Solution to this problem was to create a custom class like what JuicyFruit said.
class CustomPickerView: UIPickerView {
override init(frame: CGRect) {
super.init(frame: frame)
dataSource = self
delegate = self
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
extension CustomPickerView: UIPickerViewDelegate {
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
...
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
...
}
}
extension CustomPickerView: UIPickerViewDataSource {
func numberOfComponents(in pickerView: UIPickerView) -> Int {
...
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
...
}
}
I have button and Picker View. If i'm trying to choose item in Picker View and use text for Button's tittle it have cropped text. Why?
This is my code:
import UIKit
class ViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource
{
let array = ["Incredible", "Fantastic"]
#IBOutlet weak var pickerView: UIPickerView!
#IBOutlet weak var button: UIButton!
override func viewDidLoad()
{
super.viewDidLoad()
pickerView.delegate = self
pickerView.dataSource = self
}
override func didReceiveMemoryWarning()
{
super.didReceiveMemoryWarning()
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int
{
return array.count
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String?
{
button.setTitle(array[row], for: .normal)
return array[row]
}
func pickerView(_ pickerView: UIPickerView, rowHeightForComponent component: Int) -> CGFloat
{
return 50
}
func numberOfComponents(in pickerView: UIPickerView) -> Int
{
return 1
}
}
Screenshots of Device emulator & XCODE's Storyboard: