How do I change the border color of a Picker - swift

I have a Picker in my View and I don't know how to access the border color. This is the code:
Picker("Select a Project", selection: $selectedProjectString){
ForEach(projects, id: \.self) {
Text($0).lineLimit(1).frame(height:120)
}
}.foregroundColor(Color.white)
.frame(height: 120)
The Picker looks like this and I want the green border to be white too.
Thanks
Update:

You can achieve this with a custom extension. You can set your border color to any color. (code is below the image)
Credit to #Tamas
Tested view:
Picker("Select a Project", selection: $pick){
ForEach(animal, id: \.self) {
Text($0).lineLimit(1).frame(height:120)
}
}
.focusBorderColor(color: .red)
.foregroundColor(Color.white)
.frame(height: 120)
Extension:
extension Picker {
func focusBorderColor(color: Color) -> some View {
let isWatchOS7: Bool = {
if #available(watchOS 7, *) {
return true
}
return false
}()
let padding: EdgeInsets = {
if isWatchOS7 {
return .init(top: 17, leading: 0, bottom: 0, trailing: 0)
}
return .init(top: 8.5, leading: 0.5, bottom: 8.5, trailing: 0.5)
}()
return self
.overlay(
RoundedRectangle(cornerRadius: isWatchOS7 ? 8 : 7)
.stroke(color, lineWidth: isWatchOS7 ? 4 : 3.5)
.offset(y: isWatchOS7 ? 0 : 8)
.padding(padding)
)
}
}

Related

Selected and unselected from HStack logic

i want that when i pressed on 5th box then all left box will fill , and when i click on eg. 1 then unselected last 4 box , but not 1,
if i clicked on 2nd box then last 3 will be unselected , Thank you in Advanced
#State var SelectedAppsname = [1,2]
ForEach (1..<6){ index in
Button(action: {
let count = selectedAppsName.count
print(count, "count")
print(index,"index")
if selectedAppsName.contains(index) {
print(index, "inINdex")
for i in 0..<count {
if (index<=i) {
}
else {
let new = count - index
print(new, "new")
selectedAppsName.removeLast(new)
}
}
}
else {
for i in count + 1...index {
selectedAppsName.append(i)
}
}
}, label: {
RoundedRectangle(cornerRadius: 16).fill(Color.secondary.opacity(0.5))
.frame(width: 49.4, height: 56, alignment: .center)
})
.background(RoundedRectangle(cornerRadius: 16).fill( selectedAppsName.contains(index) ? Color(red: 1.0, green: 0.9, blue: 0.02) : Color.clear))
}
}
Make it simple:
#State private var level = 0
var body: some View {
HStack {
ForEach(0..<6) { index in
Button {
withAnimation {
level = index
}
} label: {
RoundedRectangle(cornerRadius: 5)
.foregroundColor(index > level ? .gray : .yellow)
.frame(width: 49.4, height: 56, alignment: .center)
}
}
}
}

How to align with a certain view in a VStack?

I'm trying to achieve the following layout:
As you can see, Foo is aligned with Bar horizontally, and there's a vertical line at the bottom of Foo.
I haven't managed to do it, the best I can do is the following one:
Here is the corresponding code:
import SwiftUI
struct ContentView: View {
var body: some View {
HStack {
VStack(spacing: 5) {
Text("Foo")
.padding(.bottom, 0)
getVerticalLine()
}
Text("Bar")
.padding(10)
.frame(maxWidth: .infinity, alignment: .leading)
.background(.yellow)
}
}
private func getVerticalLine() -> some View {
return Color.gray
.frame(width: 1, height: 30)
.padding(.leading, 0)
}
}
My question is: how to modify the code to achieve the expected layout?
P.S. In the end, I want to achieve something like this:
Here is the complete code that you want. You need to use multiplier for constant so that it fits in all devices.
CententView
struct ContentView: View {
var body: some View {
VStack(alignment: .leading, spacing: 10) {
FooBarView(isLastOne: false)
.padding(EdgeInsets(top: 0, leading: 10, bottom: -18, trailing: 0))
FooBarView(isLastOne: false)
.padding(EdgeInsets(top: 0, leading: 10, bottom: -18, trailing: 0))
FooBarView(isLastOne: false)
.padding(EdgeInsets(top: 0, leading: 10, bottom: -18, trailing: 0))
FooBarView(isLastOne: true)
.padding(EdgeInsets(top: 0, leading: 10, bottom: 0, trailing: 0))
}
}
}
FooBarView
struct FooBarView: View {
var isLastOne: Bool
var body: some View {
VStack(alignment: .leading, spacing: 0) {
HStack {
VStack(spacing: 5) {
Text("Foo")
.padding(.bottom, 0)
}
Text("Bar")
.padding(10)
.frame(maxWidth: .infinity, alignment: .leading)
.background(.yellow)
}
if !isLastOne {
getVerticalLine()
.padding(EdgeInsets(top: -5, leading: 12, bottom: 0, trailing: 0))
}
}
}
private func getVerticalLine() -> some View {
return Color.gray
.frame(width: 1, height: 40)
.padding(.leading, 0)
}
}
Each of your activities are a single HStack, with the time on the left. The line is just something that connects each row, not necessarily part of the row itself.
You can use a ZStack to show the gray line on the back and the rows on the front; a background on the first column will avoid overlapping the gray line.
The alignment is assured by giving the same frame width to the first column (time) and the vertical line.
Here's an example:
struct Example: View {
struct Activity: Hashable, Identifiable {
let id = UUID()
let time: String
let activity: String
}
let activities = [Activity(time: "10.00", activity: "Exercise"),
Activity(time: "11.00", activity: "Work"),
Activity(time: "12.00", activity: "Study"),
Activity(time: "13.00", activity: "Coding")]
let firstColumnWidth = 80.0
let spacingBetweenLines = 30.0
let internalPadding = 10.0
let lineHeight = 20.0
var body: some View {
ZStack {
HStack {
getVerticalLine
.frame(width: firstColumnWidth)
Spacer()
}
VStack(spacing: spacingBetweenLines) {
ForEach(activities) { item in
HStack(spacing: 5) {
Text(item.time)
.frame(height: lineHeight)
.padding(internalPadding)
.frame(width: firstColumnWidth)
.background(.white)
Text(item.activity)
.frame(height: lineHeight)
.padding(internalPadding)
.frame(maxWidth: .infinity, alignment: .leading)
.background(.yellow)
}
}
}
}
.padding()
}
private var verticalLine: some View {
let lineHeight = internalPadding * 2 + lineHeight + spacingBetweenLines
return Color.gray
.frame(width: 1, height: lineHeight * Double(activities.count - 1))
.background(.green)
}
}

SwiftUI List - selected element flashes white before drawn as planned

I have this code for a List populated by Core Data:
List {
ForEach(items.filter {
self.searchText.isEmpty ? true : $0.term!.contains(self.searchText)
}, id: \.self) { item in
Button(action: {
globalVariables.selectedItem = item
}) {
Text(item.term!)
.font(fontItems)
.disabled(true)
.foregroundColor(globalVariables.selectedItem == item ? .black : .white)
}
.listRowBackground(
Group {
if ((globalVariables.selectedItem == nil) || (item != globalVariables.selectedItem)) {
Color(UIColor.clear)
} else if item == globalVariables.selectedItem {
Color("corListaSelecao").mask(RoundedRectangle(cornerRadius: 20))
}
}
.padding(EdgeInsets(top: 0, leading: 5, bottom: 0, trailing: 5))
)
}
.onDelete(perform: deleteItems)
.onAppear{
globalVariables.selectedItem = items.first
}
.cornerRadius(20)
}
.background(Color("fundoControles"))
.cornerRadius(10)
.padding(EdgeInsets(top: 5, leading: 10, bottom: 5, trailing: 10))
This produces a list like the following picture:
Every time I select a new item on the list, the new selected element background is drawn as a white hard edge rectangle momentarily then it is drawn as is is supposed to be, orange with round corners.
I am not setting this white color anywhere.
I have this init on my ContentView:
init() {
UITextView.appearance().backgroundColor = .clear
UITableView.appearance().backgroundColor = .clear
UITableViewCell.appearance().backgroundColor = .clear
}
Where is this white color coming from?
How do I get rid of this undesired flash?
This might be default List highlight for Button, try to remove Button and use instead .onTapGesture directly to Text, like
Text(item.term!)
.font(fontItems)
.disabled(true)
.foregroundColor(globalVariables.selectedItem == item ? .black : .white)
// replacements for button to make whole row tappable
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)
.contentShape(Rectangle())
.onTapGesture {
globalVariables.selectedItem = item
}

SwiftUI - Position a rectangle relative to another view in ZStack

The task
I am creating a bar chart with SwiftUI and I am looking for the best approach to position the average indicator, the horizontal yellow line, shown in the picture below:
The goal
I need to be able to position the yellow line (rectangle with 1 point in height) relative to the bars (also rectangles) of the chart. For simplicity let us imagine that the tallest possible bar is 100 points and the average is 75. My goal is to draw the yellow line exactly 75 points above the bottom of the bars.
The problem
In my example I am placing the line on top of the chart using a ZStack. I can position the line vertically using a GeometryReader, however I do not know how to position it in reference to the bottom of the bars.
Here my playground code:
import SwiftUI
import PlaygroundSupport
struct BarChart: View {
var measurements : [Int]
var pastColor: Color
var todayColor: Color
var futureColor: Color
let labelsColor = Color(UIColor(white: 0.5, alpha: 1.0))
func monthAbbreviationFromInt(_ day: Int) -> String {
let da = Calendar.current.shortWeekdaySymbols
return da[day]
}
var body: some View {
ZStack {
VStack {
HStack {
Rectangle()
.fill(Color.yellow)
.frame(width: 12, height: 2, alignment: .center)
Text("Desired level")
.font(.custom("Helvetica", size: 10.0))
.foregroundColor(labelsColor)
}
.padding(.bottom , 50.0)
HStack {
ForEach(0..<7) { day in
VStack {
Spacer()
Text(String(self.measurements[day]))
.frame(width: CGFloat(40), height: CGFloat(20))
.font(.footnote)
.lineLimit(1)
.minimumScaleFactor(0.5)
.foregroundColor(self.labelsColor)
Rectangle()
.fill(day > 0 ? self.futureColor : self.pastColor)
.frame(width: 20, height: CGFloat(self.measurements[day]*2))
.transition(.slide)
.cornerRadius(3.0)
Text("\(self.monthAbbreviationFromInt(day))\n\(day)")
.multilineTextAlignment(.center)
.font(.footnote)
.frame(height: 20)
.lineLimit(2)
.minimumScaleFactor(0.2)
.foregroundColor(self.labelsColor)
}
.frame(height: 200, alignment: .bottom )
}
}
}
.padding()
VStack {
GeometryReader { geometry in
//Yellow line
Rectangle()
.path(in: CGRect(x: 0,
y: 350, //This is now a random value, but I should detect and use the bottom coordinate of the bars to place the line at exactly 75 points above that coordinate
width: geometry.size.width,
height: 1))
.fill(Color.yellow)
}
}
}
}
}
How can I get the relative position of the bottom of the bars so that I can place the line correctly?
Is there another approach you would suggest?
Thank you in advance
example
import SwiftUI
struct Data: Identifiable {
let id = UUID()
let value: CGFloat
let color: Color
}
struct ContentView: View {
let data = [Data(value: 35, color: .red),
Data(value: 100, color: .green),
Data(value: 70, color: .yellow),
Data(value: 25, color: .blue),
Data(value: 55, color: .orange)]
var maxValue: CGFloat {
data.map { (element) -> CGFloat in
element.value
}.max() ?? 1.0
}
var average: CGFloat {
guard !data.isEmpty else { return 0 }
let sum = data.reduce(into: CGFloat.zero) { (res, data) in
res += data.value
}
return sum / CGFloat(data.count)
}
var body: some View {
HStack {
ForEach(data) { (bar) in
Bar(t: bar.value / self.maxValue, color: bar.color, width: 20)
}
}
.background(Color.pink.opacity(0.1))
.overlay(
GeometryReader { proxy in
Color.primary.frame(height: 1).position(x: proxy.size.width / 2, y: proxy.size.height * ( 1 - self.average / self.maxValue))
}
).padding(.vertical, 100).padding(.horizontal, 20)
}
}
struct Bar: View {
let t: CGFloat
let color: Color
let width: CGFloat
var body: some View {
GeometryReader { proxy in
Path { (path) in
path.move(to: .init(x: proxy.size.width / 2, y: proxy.size.height))
path.addLine(to: .init(x: proxy.size.width / 2, y: 0))
}
.trim(from: 0, to: self.t)
.stroke(lineWidth: self.width)
.foregroundColor(self.color)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
and result
or slightly adopted to show labels

Is there a way to call a function when a SwiftUI Picker selection changes?

I would like to call a function when selectedOption's value changes. Is there a way to do this in SwiftUI similar to when editing a TextField?
Specifically, I would like to save the selected option when the user changes the selectedOption.
Here is my picker:
struct BuilderPicker: View {
let name: String
let options: Array<String>
#State var selectedOption = 0
var body: some View {
HStack {
Text(name)
.font(.body)
.padding(.leading, 10)
Picker(selection: $selectedOption, label: Text(name)) {
ForEach(0 ..< options.count) {
Text(self.options[$0]).tag($0)
}
}.pickerStyle(SegmentedPickerStyle())
.padding(.trailing, 25)
}.onTapGesture {
self.selectedOption = self.selectedOption == 0 ? 1 : 0
}
.padding(.init(top: 10, leading: 10, bottom: 10, trailing: 0))
.border(Color.secondary, width: 3)
.padding(.init(top: 0, leading: 15, bottom: 0, trailing: 15))
.font(.body)
}
}
I’m still new to SwiftUI and would love some help. Thanks!
If the #State value will be used in a View, you don't need extra variable name
struct BuilderPicker: View {
// let name: String = ""
let options: Array<String> = ["1", "2","3","4","5"]
#State var selectedOption = 0
var body: some View {
HStack {
Text(options[selectedOption])
.font(.body)
.padding(.leading, 10)
Picker(selection: $selectedOption, label: Text(options[selectedOption])) {
ForEach(0 ..< options.count) {
Text(self.options[$0]).tag($0)
}
}.pickerStyle(SegmentedPickerStyle())
.padding(.trailing, 25)}
// }.onTapGesture {
// self.selectedOption = self.selectedOption == 0 ? 1 : 0
// }
.padding(.init(top: 10, leading: 10, bottom: 10, trailing: 0))
.border(Color.secondary, width: 3)
.padding(.init(top: 0, leading: 15, bottom: 0, trailing: 15))
.font(.body)
}
}
If you need separated operation on the #State, the simplest way is adding one line : onReceive() to the view.
HStack {
Text("")
.font(.body)
.padding(.leading, 10)
Picker(selection: $selectedOption, label: Text("")) {
ForEach(0 ..< options.count) {
Text(self.options[$0]).tag($0)
}
}.pickerStyle(SegmentedPickerStyle())
.padding(.trailing, 25)}
// }.onTapGesture {
// self.selectedOption = self.selectedOption == 0 ? 1 : 0
// }
.padding(.init(top: 10, leading: 10, bottom: 10, trailing: 0))
.border(Color.secondary, width: 3)
.padding(.init(top: 0, leading: 15, bottom: 0, trailing: 15))
.font(.body)
.onReceive([self.selectedOption].publisher.first()) { (value) in
print(value)
}
The previous solution will end up in an infinite loop if you update an ObservedObject in the callback since .onReceive is also called when the View got rendered.
→ A better approach is to use a .onChange method on the Binding itself:
Picker(selection: $selectedOption.onChange(doSomething), label: Text("Hello world")) {
// ...
}
To do so you need to write an extension for Binding like described here.