I have a picker that looks like this:
With the code being this:
#State var typeSelection: String = "Socialism"
var typeOptions = ["Socialism", "Democratic Socialism", "Capitalism", "Authoritarianism"]
...
Picker("Please pick a Type", selection: $typeSelection) {
ForEach(typeOptions, id: \.self) {
Text($0)
.foregroundColor(.white)
.font(.system(size: 30, weight: .medium, design: .default))
}
}
How do I make the background color of the selected one darker, like if say it's opacity is 30% of a black color right now how do I bump that to 70%?
Related
I have an app in which the user may select a SF icon from those within a LazyHGrid. I would like to add a shadow around the selected icon and remove the shadow when deselected.
Currently, the working code below may be used to scroll the available icons and select an icon by tapping. I need help changing the view to support applying shadow to the selected element.
I tried placing the same image() and modifiers within the button action but got a Xcode warning that the ZStack initializer is unused. I also tried adding a shadow modifier to the view changing the shadow parameters with state properties set in the button action area. This applied shadow to all elements in LazyHGrid. I want the shadow applied only to the selected element.
struct ImageStore: Identifiable, Hashable {
var iconName: String
var id: Int
}
struct ContentView: View {
let rows = [
GridItem(.flexible()),
]
let colors: [Color] = [.green, .red, .yellow, .blue]
let imageName = [
ImageStore(iconName: "a.square.fill", id: 0),
ImageStore(iconName: "b.square.fill", id: 1),
ImageStore(iconName: "c.square.fill", id: 2),
ImageStore(iconName: "d.square.fill", id: 3),
ImageStore(iconName: "e.square.fill", id: 4),
ImageStore(iconName: "f.square.fill", id: 5),
ImageStore(iconName: "g.square.fill", id: 6),
]
#State private var selectedIcon: Int = 0
var body: some View {
VStack {
ScrollView (.horizontal) {
LazyHGrid( rows: rows, spacing: 20) {
ForEach(imageName, id: \.self) { image in
Button( action: {
selectedIcon = image.id
print("image name = \(image.iconName)")
print("id = \(image.id)")
print("selectedIcon = \(selectedIcon)")
}){
Image(systemName: image.iconName)
.font(.largeTitle)
.foregroundColor(.white)
.padding()
.background(colors[image.id % colors.count])
}
}
}
}
}
}
}
Perhaps I'm not understanding your question fully, but it should be as simple as using the .shadow modifier with a ternary expression, e.g.
.shadow(radius: selectedIcon == image.id ? 5 : 0)
to make sure the image doesn't have it's own shadow in addition to the background, add a .drawingGroup modifier, e.g
Button {
selectedIcon = image.id
} label: {
Image(systemName: image.iconName)
.font(.largeTitle)
.foregroundColor(.white)
.padding()
.background(colors[image.id % colors.count])
.drawingGroup()
.shadow(radius: selectedIcon == image.id ? 5 : 0)
}
I have my code set as follows:
var body: some View {
NavigationStack {
VStack {
// Contacts Scroll View
ScrollView {
LazyVGrid(columns: threeColumnGrid, spacing: 20) {
ForEach($contacts, id: \.self) { $contact in
ContactCell(firstName: $contact.firstName.wrappedValue)
}
}.padding(EdgeInsets(top: 20,
leading: 20,
bottom: 20,
trailing: 20))
}
}.background(Color(CustomColors.background.rawValue))
}
}
I would like to be able to tap on one of the grid items in order to segue into another screen, but the only solution I can come up with is NavigationLink which only inserts a link that needs to be tapped.
I need the entire grid item to be tappable without any extra text acting as a link.
Side note: I have also looked into the isActive property of NavigationLink, which worked great, but this is being deprecated in iOS 16... It's as if Apple refuses to allow us to create a collection view using swiftUI.
Figured it out. I used: navigationDestination(isPresented:destination:)
See code below:
struct ContactsGridView: View {
#Binding var contacts: [Contact]
#State var shouldPresentContactMainView = false
let threeColumnGrid = [GridItem(.flexible(), spacing: 20),
GridItem(.flexible(), spacing: 20),
GridItem(.flexible(), spacing: 20)]
var body: some View {
NavigationStack {
VStack {
// Contacts Scroll View
ScrollView {
LazyVGrid(columns: threeColumnGrid, spacing: 20) {
ForEach($contacts, id: \.self) { $contact in
ContactCell(firstName: $contact.firstName.wrappedValue)
.onTapGesture {
shouldPresentContactMainView = true
}
.navigationDestination(isPresented: $shouldPresentContactMainView) {
ContactMainView()
}
}
}.padding(EdgeInsets(top: 20,
leading: 20,
bottom: 20,
trailing: 20))
}
}.background(Color(CustomColors.background.rawValue))
}
}
}
I have a Picker like this
Picker ("", selection: $itemIndex){
ForEach (0..<items.count, id: \.self){ index in
Text(items[index])
}
}
.frame(maxWidth: .infinity)
.background(.red)
The issue is that the picker extends to the screen width but the text displaying on the Picker is not that lengthy, maybe about 1/20 of the Picker length
Now when I click on the text, the Picker menu opens up, but when I click outside the text, still within the picker visibility, the menu don't open.
In the image above, when I tap on the red side, the menu opens up, but when I tap on both green sides it doesn't
Do anyone have an idea why this is happening?
Note: the picker is not in a Form or a NavigationController
Okay... So through the help of this question, I was able to customize my own Picker style. Below is the code to achieve this
struct CustomPickerStyle: ViewModifier {
#Binding var index: Int
var items: [String]
var font: SwiftUI.Font
var padding: CGFloat
func body(content: Content) -> some View {
Menu {
content
} label: {
HStack {
if let labelText = items[index] {
Text(labelText)
.font(font)
Spacer()
Image(systemName: "triangle.fill")
.resizable()
.frame(width: 12, height: 8)
.rotationEffect(.degrees(180))
}
}
.padding(padding)
.frame(alignment: .leading)
.background(Colors.white)
}
}
}
extension View {
func customPickerStyle(index: Binding<Int>, items: [String], font: SwiftUI.Font, padding: CGFloat) -> some View {
self.modifier(CustomPickerStyle(index: index, items: items, font: font, padding: padding))
}
}
And I this is how I used it in my UI
Picker("", selection: $itemIndex){
ForEach(0..<items.count, id: \.self){ index in
Text(items[index])
}
}
.customPickerStyle(index: $itemIndex, items: items, font: .system(size: 17), padding: 10)
.frame(width: UIScreen.main.bounds.width - 50)
.overlay(RoundedRectangle(cornerRadius: 10.0).stroke(.blue, lineWidth: 1.0))
And here is the result
I assume you wanted this
Picker ("", selection: $itemIndex){
ForEach (0..<items.count, id: \.self) { index in
Text(items[index])
.frame(maxWidth: .infinity) // << here !!
}
}
.background(.red)
or this if you told about Menu
Menu {
// content here
} label: {
Text("Menu")
.frame(maxWidth: .infinity) // << here !!
}
the general rule is - give space for label which is actually shown, instead of containing control, because hit testing is usually set up by intrinsic content.
I'm trying to align my Picker hidden label values left to another TextField in my form (see attached image). What's the best way to remove the 8px padding in the displayed picker value?
import SwiftUI
struct ContactFormView: View {
var countries = ["Malaysia", "Singapore", "Japan"]
#State var name: String = ""
#State var mobile: String = ""
#State var mobileCountry: String = "Malaysia"
var body: some View {
NavigationView {
Form {
Section(header: Text("Details")) {
TextField("Name", text: $name)
.border(Color.red, width: 1)
HStack {
Picker(selection: $mobileCountry, label: EmptyView()) {
ForEach(countries, id: \.self) {
Text($0).border(Color.red)
}
}
.scaledToFit()
.labelsHidden()
.border(Color.red, width: 1)
TextField("Mobile", text: $mobile)
.border(Color.red, width: 1)
}
}
}
.navigationBarTitle("New contact")
}
}
}
Probably your best bet is to use a negative padding on your picker:
Picker(selection: $mobileCountry, label: EmptyView()) {
ForEach(countries, id: \.self) {
Text($0).border(Color.red)
}
}
.scaledToFit()
.labelsHidden()
.border(Color.red, width: 1)
.padding(.horizontal, -8)
Loading your code in Xcode 12.5, adding that negative padding aligns the text in the way you want.
Intuitively I would have chosen .padding(.leading, -8) to remove padding for the Picker's leading edge only – but in doing so, the gap between the picker and text field grows larger.
Applying the negative padding to both horizontal values keeps that gap the same for me. But I'd recommend in your code trying both, and seeing which one makes the most sense for you.
Is there an existing solution to get a horizontal picker in Swift UI?
Example for Swift: https://github.com/akkyie/AKPickerView-Swift
I ended up solving this using a horizontal scrollview, tap gestures, and state to track the selection.
#State private var index
var body: some View {
return ScrollView(.horizontal, showsIndicators: false) {
HStack {
ForEach(0..<self.items.count, id: \.self) { i in
Text("\(i)")
.foregroundColor(self.index == i ? .red : .black)
.frame(width: 20, height: 20)
.gesture(TapGesture().onEnded({ self.index = i }))
}
}
}
.frame(width: self.width, alignment: .leading)
}
You can use the Rotation Effect on a Picker and on the Picker's content. Make sure to rotate the content 90 degrees opposite from the picker or else the content will be sideways. If the picker is too big, you can manually set a height of the picker by using frame(height: _). In my case, I used frame(maxHeight: _). You might need to adjust the row indicators by using clipped() after the resize to stop them from flowing out of the picker.
I'm using images as an example but it should work with most if not all basic views.
Code:
Picker(selection: $data, label: Text("Data")) {
ForEach(dataArray, id: \.self) { imageName in
Image(imageName)
.resizable()
.scaledToFit()
.rotationEffect(Angle(degrees: 90))
}
}
.labelsHidden()
.rotationEffect(Angle(degrees: -90))
.frame(maxHeight: 100)
.clipped()