problems with #binding between structs - swiftui

in my code I have:
struct Specialty {
let type:String
let color:Color
let image:Image
}
// MARK: Search
struct Search: View {
#State var show = false
#State var txt = ""
#State var index = 1
let specialtyList = [
Specialty(type: "Cardiologia", color: Color.blue, image: Image("google1") ),
Specialty(type: "Clínica Médica", color: Color.pink, image: Image("google1")),
Specialty(type: "Dermatologia", color: Color("Color"), image: Image("google1")),
Specialty(type: "Ginecologia e Obstetrícia", color: Color.pink, image: Image("google1")),
Specialty(type: "Medicina do Trabalho", color: Color.red, image: Image("google1")),
Specialty(type: "Oftalmologia", color: Color("Color"), image: Image("google1")),
Specialty(type: "Ortopedia", color: Color.pink, image: Image("google1")),
Specialty(type: "Otorrinolaringologia", color: Color.blue, image: Image("google1")),
Specialty(type: "Pediatria", color: Color.red, image: Image("google1")),
Specialty(type: "Psiquiatria", color: Color("Color"), image: Image("google1")),
Specialty(type: "Radiologia", color: Color("Color"), image: Image("google1"))
]
}
ignore the same google1 images, I'm just trying to get the code to work first.
Then, in the View of Search, I have:
ForEach(specialtyList, id: \.type){ Specialty in
NavigationLink (destination: SearchBar()){
VStack(spacing: 18) {
HStack{
Text(Specialty.type).foregroundColor(.white)
Specialty.image
.renderingMode(.original)
.resizable()
.frame(width: 35, height: 35)
}
}
}
}
to show the information in 'let specialtyList' as a scrollView
As each world displayed works as a button, Im trying that, when I go to the destination (which is SearchBar() in this case), I want to have different information shown depending on the NavigationLink text that is pressed.
How could I do it by using the order in the list 'specialtyList' and how could a simply print, in the destination, the same name of the NavigationLink text that was pressed?

I assume this is it
Note: try to avoid same naming for type (like, Specialty, capitalised) and instance/value (like, specialty, lowercased), otherwise it might confuse and compiler and you.
ForEach(specialtyList, id: \.type){ specialty in // << named correctly
NavigationLink (destination: SearchBar(item: specialty)){ // << inject here
VStack(spacing: 18) {
HStack{
Text(specialty.type).foregroundColor(.white)
specialty.image
.renderingMode(.original)
.resizable()
.frame(width: 35, height: 35)
}
}
}
}
and now in view
struct SearchBar: View {
let item: Specialty
var body: some View {
Text(item.type)
// .. other code
}
}

Related

SwiftUI Charts extension Image: Plottable

Is there any sensible way to create sth like extension Image: Plottable and pass to .value inside BarMark. Or there is any other possibility to have images below charts instead text?
I try sth like but it of course doesn't work
extension Image: Plottable {
public var primitivePlottable: String {
????
}
public init?(primitivePlottable: String) {
self.init(primitivePlottable)
}
}
ok i found a slightly different solution to this problem using annotation modifier with position: .bottom like:
BarMark(x: .value("Shape Type", shape.type),
y: .value("Total Count", shape.count))
.annotation(position: .bottom, alignment: .center, spacing: 10, content: {
Image(shape.type)
.resizable()
.scaledToFit()
.frame(width: 50, height: 50)
})

I can't seem to figure out a way to include text and image in a same ForEach

let items = Array(1...9).map({"Image\($0)"})
let items1 = Array(11...14).map({"Image\($0)"})
let items2 = Array(15...20).map({"Image\($0)"})
let items3 = Array(21...25).map({"Image\($0)"})
let items4 = Array(26...39).map({"Image\($0)"})
struct MyData: Identifiable{
var id = UUID()
var title: String
}
var mData = [
MyData(title: "Men's Clothing"),
MyData(title: "Men's Shoes"),
MyData(title: "Men's Accessories"),
MyData(title: "Children's Clothing"),
MyData(title: "Women's Clothing"),
MyData(title: "Women's Shoes"),
MyData(title: "Women's Accessories"),
MyData(title: "Children's Shoes"),
MyData(title: "Children's Accessories")
]
#State var text = ""
let layout = [
GridItem(.flexible(minimum: 80)),
GridItem(.flexible(minimum: 80)),
GridItem(.flexible(minimum: 80)),
GridItem(.flexible(minimum: 80))
]
var body: some View {
NavigationView{
ScrollView(.vertical, showsIndicators: false){
SearchCategories(text: $text)
Spacer()
LazyVGrid(columns: layout, pinnedViews: [.sectionHeaders] ) {
Section(header: Text("Clothing & Accessories")
.font(.system(size:16))
.foregroundColor(.white)
.frame(height: 0)
.padding()
.frame(maxWidth: .infinity, alignment: .leading)
.background(Color("MainF"))
) {
ForEach(mData.filter({"\($0)".contains(text) || text.isEmpty})){ item in
VStack{
Text(item.title)
.foregroundColor(Color.white)
.frame(alignment: .center)
.font(.system(size: 10))
}
}
}
}
LazyVGrid(columns: layout, pinnedViews: [.sectionHeaders] ){
Section(header: Text("Vehicles")
.font(.system(size:16))
.foregroundColor(.white)
.frame(height: 0)
.padding()
.frame(maxWidth: .infinity, alignment: .leading)
.background(Color("MainF"))
) {
ForEach(items1, id: \.self) {
item in
VStack{
Image(item)
.resizable()
.aspectRatio(contentMode: .fit)
.padding()
Button("Motorbike"){
}
.foregroundColor(Color.white)
.font(.system(size: 12))
}
}
}
}
I am trying to show a gird of image and text together. But I am not able to implement that at once. It's either text grid or image gird.
I have implemented mData for all the text I want below the image that I am pulling from items array. I'm trying to achieve the grid layout through LazyVgrid. I have also added pinnedViews to add headings for each grid.
I am recently learning switfUI and solution for this will be very helpful.
Welcome to Stack Overflow! Please take the tour and see: How do I ask a good question? and How to create a Minimal, Reproducible Example (MRE). It is very helpful to post a buildable MRE. The question itself was well asked.
Here is an example to get you started. The key is to have the image and the text contained in the same data model, so they are accessible at the same time. I added an image variable to the MyData struct, and you can see how that allows the view to come together easily. Since I don't have access to your images, I used SF Symbols instead.
struct MyData: Identifiable{
let id = UUID()
let title: String
let image: String
}
struct GridTextAndImageView: View {
let myData = [
MyData(title: "Pencil", image: "pencil"),
MyData(title: "Trash Can", image: "trash"),
MyData(title: "Paper Airplane", image: "paperplane.fill"),
MyData(title: "Umbrella", image: "umbrella.fill"),
MyData(title: "Megaphone", image: "megaphone.fill"),
MyData(title: "Speaker", image: "speaker.fill"),
MyData(title: "Flashlight", image: "flashlight.on.fill"),
MyData(title: "Camera", image: "camera"),
MyData(title: "Wrench", image: "wrench"),
MyData(title: "Screwdriver", image: "screwdriver.fill"),
MyData(title: "Paintbrush", image: "paintbrush"),
MyData(title: "Printer", image: "printer"),
MyData(title: "Briefcase", image: "briefcase.fill"),
MyData(title: "AirPods Max", image: "airpodsmax"),
]
let layout = [
GridItem(.flexible(minimum: 80)),
GridItem(.flexible(minimum: 80)),
GridItem(.flexible(minimum: 80)),
GridItem(.flexible(minimum: 80))
]
var body: some View {
NavigationView{
ScrollView(showsIndicators: false) {
LazyVGrid(columns: layout, spacing: 25, pinnedViews: [.sectionHeaders] ) {
Section(header: Text("Various SF Symbols")
.font(.system(size:16))
.foregroundColor(.white)
.frame(height: 0)
.padding()
.frame(maxWidth: .infinity, alignment: .leading)
.background(Color.orange)
) {
ForEach(myData) { item in
VStack{
Image(systemName: item.image)
.resizable()
.aspectRatio(contentMode: .fit)
.padding()
.foregroundColor(.white)
.frame(width: 80, height: 80)
.background(LinearGradient(colors: [Color.blue, Color.blue.opacity(0.4)], startPoint: .top, endPoint: .bottom))
.clipShape(RoundedRectangle(cornerRadius: 15))
Text(item.title)
.frame(alignment: .center)
.font(.system(size: 10))
}
}
}
}
}
}
}
}

How would you make Icons like in the Settings App (SwiftUI)

How could you achieve Icons like that?
I know that the base is this:
Image(systemName: "person.fill")
And than you could give it a background-Color:
Image(systemName: "person.fill")
.background(Color.blue)
To get rounded corners you could just add cornerRadius:
Image(systemName: "person.fill")
.background(Color.blue)
.cornerRadius(5)
But how would you make it that each of the items is in a square box with the same size?
Because SF Symbols don't have the same size.
And I don't want to make this:
Image(systemName: "person.fill")
.frame(width: 20, height: 20)
.background(Color.blue)
.cornerRadius(5)
The frame modifier would destroy the ability of SF Symbols to match with the preferred Font Size of the User.
Is there an other solution?
Or do you think the Settings App is done with .frame()?
Okay, I found an answer at Medium.
He works with Labels and adds an custom Modifier to them.
The Modifier looks like that:
struct ColorfulIconLabelStyle: LabelStyle {
var color: Color
var size: CGFloat
func makeBody(configuration: Configuration) -> some View {
Label {
configuration.title
} icon: {
configuration.icon
.imageScale(.small)
.foregroundColor(.white)
.background(RoundedRectangle(cornerRadius: 7 * size).frame(width: 28 * size, height: 28 * size).foregroundColor(color))
}
}
}
I did some changes:
struct ColorfulIconLabelStyle: LabelStyle {
var color: Color
func makeBody(configuration: Configuration) -> some View {
Label {
configuration.title
} icon: {
configuration.icon
.font(.system(size: 17))
.foregroundColor(.white)
.background(RoundedRectangle(cornerRadius: 7).frame(width: 28, height: 28).foregroundColor(color))
}
}
}
You can use it like that:
NavigationLink {
//Destination
} label: {
Label("Your Text", systemImage: "Your Image").labelStyle(ColorfulIconLabelStyle(color: .green))
}
This achieves a very native look :)
As I mentioned, full credits to Luca J.
I recommend this down way for you the update for my answer would be reading the device is zoomed or not! Then we could gave correct size for your UI, you can change your wished size in class.
struct ContentView: View {
var body: some View {
CustomButtonView(string: "gear", action: { print("setting!") })
CustomButtonView(string: "lasso.sparkles", action: { print("lasso!") })
CustomButtonView(string: "xmark.bin", action: { print("xmark!") })
CustomButtonView(string: "command", action: { print("command!") })
CustomButtonView(string: "infinity", action: { print("infinity!") })
}
}
struct CustomButtonView: View {
let string: String
let action: (() -> Void)?
init(string: String, action: (() -> Void)? = nil) {
self.string = string
self.action = action
}
#State private var tapped: Bool = Bool()
var body: some View {
Image(systemName: string)
.resizable()
.scaledToFit()
.frame(width: DeviceReader.shared.size - 5.0, height: DeviceReader.shared.size - 5.0)
.foregroundColor(Color.white)
.padding(5.0)
.background(tapped ? Color.blue : Color.gray)
.cornerRadius(10.0)
.onTapGesture { tapped.toggle(); action?() }
.animation(.interactiveSpring(), value: tapped)
}
}
class DeviceReader: ObservableObject {
let size: CGFloat
init() {
switch UIDevice.current.userInterfaceIdiom {
case .phone: self.size = 30.0
case .pad: self.size = 40.0
case .mac: self.size = 50.0
default: self.size = 30.0 }
}
static let shared: DeviceReader = DeviceReader()
}
I was looking at this question and your answer to it because this would be a useful thing to have. However, your answer does not allow the SFFont to scale with user preferences, and the answer you found on the Medium post does not scale well, as you can't just scale up and down with theses things. They look weird. If you run it in the simulator and change the Text setting, your will see what I mean.
I would simply use a .frame that changes it's size based off a preference key on the SF Symbol itself, and giving it a bit of padding extra. You could also simply add .padding() before your .background(), but the background would not necessarily be square. This method will set the width and height of the frame to slightly more than the biggest dimension of the SF Symbol, and it will fluidly change its size, not only allowing you to drop a .font() on it, but also handle the dynamic font sizes. This is a pure SwiftUI answer, using no UIKit.
struct ColoredIconView: View {
let imageName: String
let foregroundColor: Color
let backgroundColor: Color
#State private var frameSize: CGSize = CGSize(width: 30, height: 30)
#State private var cornerRadius: CGFloat = 5
var body: some View {
Image(systemName: imageName)
.overlay(
GeometryReader { proxy in
Color.clear
.preference(key: SFSymbolKey.self, value: max(proxy.size.width, proxy.size.height))
}
)
.onPreferenceChange(SFSymbolKey.self) {
let size = $0 * 1.05
frameSize = CGSize(width:size, height: size)
cornerRadius = $0 / 6.4
}
.frame(width: frameSize.width, height: frameSize.height)
.foregroundColor(foregroundColor)
.background(
RoundedRectangle(cornerRadius: cornerRadius)
.fill(backgroundColor)
)
}
}
fileprivate struct SFSymbolKey: PreferenceKey {
typealias Value = CGFloat
static var defaultValue = CGFloat.zero
static func reduce(value: inout Value, nextValue: () -> Value) {
value += nextValue()
}
}
Use it like this:
ColoredIconView(imageName: "airplane", foregroundColor: .white, backgroundColor: .orange)
.font(.body)

SwiftUI NavigationItem dynamic data sending incorrect parameter

I have what I thought was a simple task, but it appears otherwise. As part of my code, I am simply displaying some circles and when the user clicks on one, it will navigate to a new view depending on which circle was pressed.
If I hard code the views in the NavigationLink, it works fine, but I want to use a dynamic array. In the following code, it doesn't matter which circle is pressed, it will always display the id of the last item in the array; ie it passes the last defined dot.destinationId to the Game view
struct Dot: Identifiable {
let id = UUID()
let x: CGFloat
let y: CGFloat
let color: Color
let radius: CGFloat
let destinationId: Int
}
struct ContentView: View {
var dots = [
Dot(x:10.0,y:10.0, color: Color.green, radius: 12, destinationId: 1),
Dot(x:110.0,y:10.0, color: Color.green, radius: 12, destinationId: 2),
Dot(x:110.0,y:110.0, color: Color.blue, radius: 12, destinationId: 3),
Dot(x:210.0,y:110.0, color: Color.blue, radius: 12, destinationId: 4),
Dot(x:310.0,y:110.0, color: Color.red, radius: 14, destinationId: 5),
Dot(x:210.0,y:210.0, color: Color.blue, radius: 12, destinationId: 6)]
var lineWidth: CGFloat = 1
var body: some View {
NavigationView {
ZStack
Group {
ForEach(dots){ dot in
NavigationLink(destination: Game(id: dot.destinationId))
{
Circle()
.fill(dot.color)
.frame(width: dot.radius, height: dot.radius)
.position(x:dot.x, y: dot.y )
}
}
}
.frame(width: .infinity, height: .infinity, alignment: .center )
}
.navigationBarTitle("")
.navigationBarTitleDisplayMode(.inline)
}
}
struct Game: View {
var id: Int
var body: some View {
Text("\(id)")
}
}
I tried to send the actual view as i want to show different views and used AnyView, but the same occurred It was always causing the last defined view to be navigated to.
I'm really not sure what I a doing wrong, any pointers would be greatly appreciated
While it might seem that you're clicking on different dots, the reality is that you have a ZStack with overlapping views and you're always clicking on the top-most view. You can see when you tap that the dot with ID 6 is always the one actually getting pressed (notice its opacity changes when you click).
To fix this, move your frame and position modifiers outside of the NavigationLink so that they affect the link and the circle contained in it, instead of just the inner view.
Also, I modified your last frame since there were warnings about an invalid frame size (note the different between width/maxWidth and height/maxHeight when specifying .infinite).
struct ContentView: View {
var dots = [
Dot(x:10.0,y:10.0, color: Color.green, radius: 12, destinationId: 1),
Dot(x:110.0,y:10.0, color: Color.green, radius: 12, destinationId: 2),
Dot(x:110.0,y:110.0, color: Color.blue, radius: 12, destinationId: 3),
Dot(x:210.0,y:110.0, color: Color.blue, radius: 12, destinationId: 4),
Dot(x:310.0,y:110.0, color: Color.red, radius: 14, destinationId: 5),
Dot(x:210.0,y:210.0, color: Color.blue, radius: 12, destinationId: 6)]
var lineWidth: CGFloat = 1
var body: some View {
NavigationView {
ZStack {
Group {
ForEach(dots){ dot in
NavigationLink(destination: Game(id: dot.destinationId))
{
Circle()
.fill(dot.color)
}
.frame(width: dot.radius, height: dot.radius) //<-- Here
.position(x:dot.x, y: dot.y ) //<-- Here
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center )
}
.navigationBarTitle("")
.navigationBarTitleDisplayMode(.inline)
}
}
}

ForEach view can't apply the view modifier to selected view

I have a simple horizontal list view that displays the list of colours and the problem is that I don't know how to apply border to only one selected view, for now it's adding the white border to all views in the list on didTap, please help if you know how to achieve it, thanks 🙌
Here's the code:
struct Colour: Identifiable {
var id: Int
var color: Color
}
struct ContentView: View {
#State private var didTap = false
#State private var colors: [Colour] = [
Colour(id: 1, color: .black),
Colour(id: 2, color: .yellow),
Colour(id: 3, color: .orange),
Colour(id: 4, color: .green),
Colour(id: 5, color: .red),
Colour(id: 6, color: .blue),
Colour(id: 7, color: .pink),
Colour(id: 8, color: .purple),
]
var body: some View {
ZStack {
Color.gray.opacity(0.2)
.ignoresSafeArea()
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 4) {
ForEach(colors) { color in
Rectangle()
.fill(color.color)
.frame(width: 100, height: 150)
.overlay(
Rectangle()
.stroke(didTap ? Color.white : .clear, lineWidth: 5)
.animation(.spring())
)
.padding(5)
.onTapGesture {
didTap.toggle()
print(color.id)
}
}
}
.padding(.leading, 8)
}
}
}
}
You need to store id of selected color instead of bool.
Here is simple demo (deselect on tap on same element is your excise). Tested with Xcode 12.5 / iOS 14.5
struct ContentView: View {
#State private var selected = -1 // << here !!
#State private var colors: [Colour] = [
Colour(id: 1, color: .black),
Colour(id: 2, color: .yellow),
Colour(id: 3, color: .orange),
Colour(id: 4, color: .green),
Colour(id: 5, color: .red),
Colour(id: 6, color: .blue),
Colour(id: 7, color: .pink),
Colour(id: 8, color: .purple),
]
var body: some View {
ZStack {
Color.gray.opacity(0.2)
.ignoresSafeArea()
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 4) {
ForEach(colors) { color in
Rectangle()
.fill(color.color)
.frame(width: 100, height: 150)
.overlay(
Rectangle()
.stroke(selected == color.id ? Color.white : .clear, lineWidth: 5)
.animation(.spring())
)
.padding(5)
.onTapGesture {
selected = color.id // << here !!
print(color.id)
}
}
}
.padding(.leading, 8)
}
}
}
}