How to get a horizontal picker for Swift UI? - swiftui

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()

Related

How do i implement .tag when jumping to a posiiton in a scrollview?

I have added the .id(1) to the positions in the scrollview and can get it to work as expected if i add a button inside the scrollview but i want to use a picker to jump to the .id and outside the scrollview.
Im new to this.
I have this code:
if i use this button it works as expected although its placed inside the scrollview...
Button("Jump to position") {
value.scrollTo(1)
}
This is my picker...
// Main Picker
Picker("MainTab", selection: $mainTab) {
Text("iP1").tag(1)
Text("iP2").tag(2)
Text("Logo").tag(3)
Text("Canvas").tag(4)
}
.frame(width: 400)
.pickerStyle(.segmented)
ScrollViewReader { value in
ScrollView (.vertical, showsIndicators: false) {
ZStack {
RoundedRectangle(cornerRadius: 20)
// .backgroundStyle(.ultraThinMaterial)
.foregroundColor(.black)
.opacity(0.2)
.frame(width: 350, height:185)
// .foregroundColor(.secondary)
.id(1)
There are 2 things you are mixing here:
The tag modifier is to differentiate elements among certain selectable views. i.e. Picker TabView
You can't access the proxy reader from outside unless you make it available. In other words the tag in the Picker and the ScrollViewReader does not have a direct relationship, you have to create that yourself:
import SwiftUI
struct ScrollTest: View {
#State private var mainTab = 1
#State private var scrollReader: ScrollViewProxy?
var body: some View {
// Main Picker
Picker("MainTab", selection: $mainTab) {
Text("iP1").tag(1)
Text("iP2").tag(2)
Text("Logo").tag(3)
Text("Canvas").tag(4)
}
.frame(width: 400)
.pickerStyle(.segmented)
.onChange(of: mainTab) { mainTab in
withAnimation(.linear) {
scrollReader?.scrollTo(mainTab, anchor: .top)
}
}
ScrollViewReader { value in
ScrollView (.vertical, showsIndicators: false) {
ForEach(1...4, id: \.self) { index in
ZStack {
RoundedRectangle(cornerRadius: 20)
.foregroundColor(.black)
.opacity(0.2)
.frame(width: 350, height: 500)
Text("index: \(index)")
}
.id(index)
}
}
.onAppear {
scrollReader = value
}
}
}
}

SwiftUI - onTapGesture on HStack works, until it stops working

We have a custom view, that we use in forms (its main feature is to be able to show a nice border on if there is a validation error on the form):
struct ButtonNavigationLink: View {
var label: String
var markingType: MarkingType = .none
var action: () -> Void
var body: some View {
GeometryReader { geometry in
HStack {
Text(label)
.offset(x: 5, y: 0)
Spacer()
Image("PfeilRechts")
}
.frame(width: geometry.size.width + 10, height: geometry.size.height, alignment: .leading)
.padding(.trailing, 5)
.border(markingType.getMarkingColor())
.offset(x: -5, y: 0)
// to allow click on the whole width
.contentShape(Rectangle())
.onTapGesture {
print("in ButtonNavigationLink")
self.action()
}
}
}
}
The onTapGesture works well, until it doesn't work anymore.
It happens if we open other sheets and then eventually come to this one.
The funny thing is, that - when is not working anymore - if you scroll, even so lightly, the form containing it, it starts working again. The form is not disabled

SwiftUI - How to initialize only one SectionView in ScrollView using onTapGesture

My problem is that when user presses on the item in my ScrollView all of the SectionView items changes. What I want to do is to change only one item. I'm trying to change item image when user taps on it and then when user taps on another item to bring back previous item image and change the current item image.
Take a look at my ScrollView:
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 15) {
ForEach(1..<41) { item in
GeometryReader { geometry in
SectionView(number: item, pressed: pressed, firstElement: (item == 1) ? true : false)
.rotation3DEffect(Angle(degrees:
Double(geometry.frame(in: .global).minX - 30) / -20
), axis: (x: 0, y: 10, z: 0))
.onTapGesture {
self.pressed.toggle()
SectionView(number: item, pressed: self.pressed, firstElement: (item == 1) ? true : false)
print("touched item \(item)")
}
})
}
}
}
Here's my SectionView:
struct SectionView: View {
var number: Int
var pressed: Bool
var firstElement: Bool
var body: some View {
ZStack(alignment: .bottom) {
Image(pressed ? "t1" : "t\(number)")
.resizable()
.frame(maxWidth: .infinity)
.frame(maxHeight: .infinity)
.aspectRatio(contentMode: .fill)
Spacer()
Text(firstElement ? "" : "Pride")
.font(.system(size: 12, weight: .black))
.foregroundColor(.white)
.frame(maxWidth: .infinity, alignment: .center)
.padding(.bottom, 15)
}
.frame(width: 60, height: 80)
//.background(section.image)
.cornerRadius(7)
//.shadow(color: section.image.opacity(0.15), radius: 8, x: 0, y: 10)
}
}
I tried putting your code into some kind of buildable state, but I couldn't get it to compile or run, so I may be completely misinterpreting what you are trying to do. However, it seems to me you are using pressed as if you are capturing the state at the time of the press on that particular SectionView. However, it would have to be a State var to compile, so it will update all of the views, which is what I believe you are seeing. I think what you need to do is change pressed to be an Int that keeps track of the SectionView that was pressed, and compare it to number which lets the particular SectionView keep track of its identity.
Also, I don't think you want the SectionView in the tap gesture. I think you just want to change the state of the pressed variable and let it work its way through the hierarchy.

SwuftUI Gesture location detect clicks out of View border

If I put several Views in a row with no spaces (or very little space between them), and attach some Gesture action, I can't correctly detect, which one is tapped. It looks like there is some padding inside gesture that I can't remove.
here's the example code:
struct ContentView: View {
#State var tap = 0
#State var lastClick = CGPoint.zero
var body: some View {
VStack{
Text("last tap: \(tap)")
Text("coordinates: (x: \(Int(lastClick.x)), y: \(Int(lastClick.y)))")
HStack(spacing: 0){
ForEach(0...4, id: \.self){ind in
RoundedRectangle(cornerRadius: 10)
.foregroundColor(Color.gray)
.overlay(Text("\(ind)"))
.overlay(Circle()
.frame(width: 4, height: 4)
.foregroundColor(self.tap == ind ? Color.red : Color.clear)
.position(self.lastClick)
)
.frame(width: 40, height: 50)
//.border(Color.black, width: 0.5)
.gesture(DragGesture(minimumDistance: 0)
.onEnded(){value in
self.tap = ind
self.lastClick = value.startLocation
}
)
}
}
}
}
}
and behavior:
I want Gesture to detect 0 button clicked when click location goes negative. Is there a way to do that?
I spent few hours with that problem, and just after I post this question, I found solution.
it was so simple - just to add a contentShape modifier
...
.frame(width: 40, height: 50)
.contentShape(Rectangle())
.gesture(DragGesture(minimumDistance: 0)
...

How to scale the contentview to multiple screen sizes? SwiftUI

Newbie here! I am building a quiz app using Swiftui, I built the view controller by previewing it in an iPhone 11 simulator.
And I thought the controlview would fit other iPhone sizes, like iPhone 8. Because Swiftui has a built-in auto layout.
But when I run the iPhone 8 simulator some of the content in the control view is not visible because they are below the screen.
Is there a way to fix it?
I tried to play with multiple Spacer() and different paddings but I can't seem to make it look good on both screen at the same time.
This is my code:
import SwiftUI
struct questionOne: View {
#State var totalClicked: Int = 0
#State var showDetails = false
#State var isSelected = false
var body: some View {
VStack {
TRpic().frame(width: 350.0, height: 233.0).cornerRadius(10).padding(.top, 80)
Spacer()
Text(verbatim: "What's the capital of Turkey?")
.font(.title)
.padding(.bottom, 60)
.frame(height: 100.0)
Button(action: {}) {
Text("Istanbul")
}.buttonStyle(MyButtonStyle())
Spacer()
Button(action: {self.isSelected.toggle()}) {
Text("Ankara")
}.buttonStyle(SelectedButtonStyle(isSelected: $isSelected))
Spacer()
Button(action: {}) {
Text("Athens")
} .buttonStyle(MyButtonStyle())
Spacer()
NavigationLink(destination: questionTwo()) {
VStack {
Text("Next Question")
Adview().frame(width: 150, height: 60)
}
}
}.edgesIgnoringSafeArea(.top)
}
}
struct MyButtonStyle: ButtonStyle {
func makeBody(configuration:
Self.Configuration) -> some View {
configuration.label
.padding(20)
.foregroundColor(.white)
.background(configuration.isPressed ? Color.red : Color.gray)
.cornerRadius(10.0)
}
}
struct SelectedButtonStyle: ButtonStyle {
#Binding var isSelected: Bool
public func makeBody(configuration: Self.Configuration) -> some View {
configuration.label
.padding(20)
.foregroundColor(.white)
.background(isSelected ? Color.green : Color.gray)
.cornerRadius(10.0)
}
}
enter image description here
Screenshot
Being in the given context I guess you do not want a scroll view, so regarding spacing I suggest using a VStack with spacing parameter VStack(alignment: .center, spacing: n){ ... } and remove the Spacers, if between 2 views you need another distance than n, just use padding to add some extra space.
This should adjust everything to fit the height of any screen, including the image, so do not need a fixed frame for it.
But, you might have a very wide image that could go beyond safe area, so, you could set a maximum width for the image as being the screen width
struct questionOne: View {
var body: some View {
GeometryReader { geometryProxy in
VStack(alignment: .center, spacing: 20) {
TRpic().frame(maxWidth: geometryProxy.size.width, alignment: .center)
.padding([.leading, .trailing], 10)
.......
}
}
}