SwiftUI - LazyVStack pinnedViews in another - swift

How to create something like this
LazyVStack(spacing: 0, pinnedViews: [.sectionHeaders]) {
Section(header: Text("foo") ) {
LazyVStack(spacing: 0, pinnedViews: [.sectionHeaders]) {
Section (header: Text("bar") ) {
Text("1")
Text("2")
...
}
Section (header: Text("baz") ) {
Text("1")
Text("2")
...
}
}
}
}
I need two fixed header but in this solution headers colide when fixed position
Thanks

You can give the sub headers a background (in white / bg color), and also have to push the first header back using zIndex.
struct ContentView: View {
var body: some View {
ScrollView {
LazyVStack(alignment: .leading, pinnedViews: [.sectionHeaders]) {
Section(header:
Text("Overall Header")
.zIndex(-1) // zIndex here
) {
LazyVStack(alignment: .leading, pinnedViews: [.sectionHeaders]) {
Section (header:
Text("Header One")
// give background here
.frame(maxWidth: .infinity, alignment: .leading)
.background(.gray)
) {
ForEach(0..<30) { item in
Text("Item \(item)")
}
}
Section (header:
Text("Header Two")
// give background here
.frame(maxWidth: .infinity, alignment: .leading)
.background(.gray)
) {
ForEach(0..<50) { item in
Text("Item \(item)")
}
}
}
}
}
}
.font(.title2)
.padding()
}
}

Here is some hack :)
It' using the package "SwiftUITrackableScrollView" to be found here:
"https://github.com/maxnatchanon/trackable-scroll-view".
But there are several solutions for a trackable scrollview you can find, or implement one by yourself.
It works fine, the only problem is to get/define the relevant positions of headers. Its easy if you have a predefined/fixed item height.
import SwiftUI
import SwiftUITrackableScrollView
struct MyHeaders: Identifiable {
let id = UUID()
let title: String
let position: CGFloat
var active: Bool = false
}
struct ContentView: View {
#State private var offset = CGFloat.zero // Content offset available to use
#State private var myHeaders = [
MyHeaders(title: "Outer Header One", position: 0),
MyHeaders(title: "Inner Header One", position: 0),
MyHeaders(title: "Inner Header Three", position: 264),
MyHeaders(title: "Outer Header Two", position: 525),
MyHeaders(title: "Inner Header Three", position: 525),
]
var body: some View {
TrackableScrollView(.vertical, showIndicators: false, contentOffset: $offset) {
LazyVStack(alignment: .leading) {
Text("Outer Header One")
Text("Inner Header One")
ForEach(0..<10) { item in
Text("Item \(item)")
}
Text("Inner Header Two")
ForEach(0..<10) { item in
Text("Item \(item)")
}
Text("Outer Header Two")
Text("Inner Header Three")
ForEach(0..<30) { item in
Text("Item \(item)")
}
}
}
.onChange(of: offset) { newValue in
print(offset)
for i in 0..<myHeaders.count {
myHeaders[i].active = myHeaders[i].position < offset
}
}
.overlay(
VStack(alignment: .leading) {
ForEach(myHeaders) { header in
if header.active {
Text(header.title)
}
}
}
.frame(maxWidth: .infinity, alignment: .leading)
.foregroundColor(.orange)
.background(.background)
, alignment: .topLeading
)
.font(.title2)
.padding()
}
}

Related

SwiftUI If the scroll view has a header, it will be overlaid on the header when doing ScrollTo(id:)

import SwiftUI
struct ContentView: View {
var body: some View {
FirstTabView()
}
}
struct FirstTabView: View {
let items = (0..<40)
var body: some View {
ScrollViewReader(content: { proxy in
ScrollView {
LazyVStack(pinnedViews: [.sectionHeaders]) {
Section(header: header().id("Header")
.onTapGesture {
proxy.scrollTo(20, anchor: .top)
}
) {
ForEach(items,id: \.self) {(item) in
Text("Item is \(item)")
.frame(width: UIScreen.main.bounds.width, height: 44)
.padding(.horizontal)
.background(Color.red)
.id(item)
}
}
}
}
.frame(width: UIScreen.main.bounds.width)
})
}
func header() -> some View {
HStack {
Text("Header")
.bold()
Spacer()
}.padding([.top,.bottom])
}
}
When I do ScrollTo(id:), I want the item to come under the pinned header. Is there a solution? Help
tried -> scrollTo(id: 20, anchor: .top)
expected to happen -> 20 rows below HeaderView
actually resulted -> It overlaps the HeaderView

Adding Navigationlink to view clears results

I added a navigationLink to the view and it now comes up empty. If I place the navigationLink below the scrollview, it works, but I cannot pass the required value from the foreach loop to the next view.
import SwiftUI
struct CurrentRatesView: View {
let columns: [GridItem] = Array(repeating: GridItem(.flexible(), spacing: 1), count: 5)
#StateObject private var lendersViewModel = LendersViewModel()
var body: some View {
ScrollView{
LazyVGrid(columns: columns, spacing: 1, pinnedViews: .sectionHeaders) {
ForEach(lendersViewModel.lenders, id: \.key) { lender in
NavigationLink {
HistoricalRatesView(lender: lender.financial_institution)
}label: {
Section(header: Text(lender.financial_institution)
.font(Font.custom(Design.PrimaryFontSemiBold, size: 20))
.frame(maxWidth: .infinity)
.background(Color.gray)
){
// table cell headers
TBLCellHeader(title:"1 Year")
TBLCellHeader(title:"2 Year")
TBLCellHeader(title:"3 Year")
TBLCellHeader(title:"4 Year")
TBLCellHeader(title:"5 Year")
// table cell values
TBLCellValue(value: lender.one_year)
TBLCellValue(value: lender.two_year)
TBLCellValue(value: lender.three_year)
TBLCellValue(value: lender.four_year)
TBLCellValue(value: lender.five_year)
}
}
}
.padding(10.0)
}
.onAppear{
lendersViewModel.fetchLenders()
}
}
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .principal) {
VStack{
Text("Mortgage Rates")
.font(Font.custom(Design.PrimaryFontSemiBold, size: 20))
.font(Font.custom(Design.PrimaryFontSemiBold, size: 14))
}
}
}
}
}

Utilize buttons as pickers to pick between map views

So I want to be able to utilize a picker so that I can choose between different views, but the only option that I found was a picker, which outputs the following output:
I want to have a custom solution where I can have three buttons, and choose between those similar to how a picker is chosen, here is the look that I'm attempting to achieve:
The code below renders the buttons perfectly, but the Picker don't render the contents of the button nor the modifiers, how would I be able to achieve clicking on each individual button similar to how a picker is selected?
The Picker also have a .tag(0), .tag(1) that can be utilized, how would this work on buttons?
Here is the code:
struct MapDisplaySheetView: View {
#ObservedObject var mapSettings = MapSettings()
#State var mapType = 0
#State var allItems: [String] = [
"Standard",
"Hybrid",
"Image",
]
var body: some View {
VStack(spacing: 0) {
// MARK: Map Type
HStack {
Picker(
"Map Type",
selection: $mapType,
content: {
ForEach(allItems, id: \.self) { item in
VStack {
HStack {
VStack {
Text(item)
.font(.footnote.weight(.bold))
.frame(maxWidth: .infinity, alignment: .leading)
.clipped()
.foregroundColor(.primary)
}
}
.padding(8)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.clipped()
}
.border(.red)
.background {
RoundedRectangle(cornerRadius: 4, style: .continuous)
.foregroundColor(Color(.systemFill))
}
.foregroundColor(Color(.quaternaryLabel))
.cornerRadius(8)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.clipped()
}
})
.pickerStyle(SegmentedPickerStyle())
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
.clipped()
.padding(.bottom, 4)
}
.padding(16)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.clipped()
}
}
Here is what I attempted, but it's not working, I am not getting a log.info() output when clicking buttons:
struct MapDisplaySheetView: View {
#ObservedObject var mapSettings = MapSettings()
#State var mapType = 0
#State var allItems: [String] = [
"Standard",
"Hybrid",
"Image",
]
var body: some View {
VStack(spacing: 0) {
HStack {
ForEach(allItems, id: \.self) { item in
VStack {
HStack {
VStack {
Button(action: {
// Action
}, label: {
Text(item)
})
.tag(mapSettings.mapType)
.onChange(of: mapType) { newValue in
mapSettings.mapType = newValue
log.info("We have selected \(newValue)")
}
}
}
}
}
}
}
}
}
you could try something like this:
struct MapDisplaySheetView: View {
#ObservedObject var mapSettings = MapSettings()
#State var allItems: [String] = ["Standard", "Hybrid", "Image"]
var body: some View {
HStack {
ForEach(allItems, id: \.self) { item in
Button(action: {
mapSettings.mapType = item
print("We have selected \(item)")
}, label: {
Text(item)
})
.buttonStyle(.bordered)
}
}
}
}
EDIT-1:
if the mapType is an Int, then use:
struct MapDisplaySheetView: View {
#ObservedObject var mapSettings = MapSettings()
#State var allItems: [String] = ["Standard", "Hybrid", "Image"]
var body: some View {
HStack {
ForEach(allItems, id: \.self) { item in
Button(action: {
switch item {
case "Standard": mapSettings.mapType = 0
case "Hybrid": mapSettings.mapType = 1
case "Image": mapSettings.mapType = 2
default: mapSettings.mapType = 0
}
print("We have selected \(item) mapSettings: \(mapSettings.mapType) ")
}, label: {
Text(item)
})
.buttonStyle(.bordered)
}
}
}
}

How do I fix buttons not being tappable in a ForEach loop?

I have a view which contains a button. The view with the button is created from a forEach loop. For some reason only some buttons are tappable and others are not.
The parent view contains a NavigationView, a scroll view inside the NavigationView, a lazyVStack inside of the scroll view, a forEachloop in that lazyVStack and in that for loop is the child view that contains the button.
struct ContentView: View {
let peoples:[Person] = Bundle.main.decode("data.json")
var body: some View {
let columns = [
GridItem(.flexible(minimum: 300), spacing: 10)
]
NavigationView {
ScrollView(.vertical) {
LazyVStack {
ForEach(peoples, id: \.self) { person in
PersonView(name: person.Name, age: person.Age)
}
}
.navigationTitle("A list of people")
.navigationViewStyle(DefaultNavigationViewStyle())
.padding()
}
}
}
}
The child view is bellow. I suspect the scroll view is stealing the user input, but I am not sure why or how to overcome it. Some buttons are tapeable and some are not.
struct PersonView: View {
#Environment(\.colorScheme) var colorScheme
var name: String
var age: Int
var body: some View {
VStack(alignment:.leading) {
Image("randoPerson")
.resizable()
.scaledToFill()
.frame(minWidth: nil, idealWidth: nil,
maxWidth: UIScreen.main.bounds.width, minHeight: nil,
idealHeight: nil, maxHeight: 300, alignment: .center)
.clipped()
VStack(alignment: .leading, spacing: 6) {
Text("name")
.fontWeight(.heavy)
.padding(.leading)
Text("Age \(age)")
.foregroundColor(Color.gray)
.padding([.leading,.bottom])
Button(action: { print("I was tapped") }) {
HStack {
Image(systemName: "message.fill")
.font(.title)
.foregroundColor(.white)
.padding(.leading)
Text("Message them")
.font(.subheadline)
.foregroundColor(.white)
.padding()
}
.background(Color.blue)
}
.padding()
}
.background(Color(UIColor.systemBackground).cornerRadius(15))
.shadow(color:colorScheme == .dark
? Color.white.opacity(0.2)
: Color.black.opacity(0.2),
radius: 7, x: 0, y: 2)
}
}
}
To fix the issue, you can add an id: UUID associated to a Person and iterate between the Person, inside the ForEach using their ID.
You will also noticed that I added the value as lowercases to respect the Swift convention.
struct Person {
let id: UUID // Add this value
var name: String
var age: Int
}
So here is the ContentView with the id replacing \.self:
struct ContentView: View {
let peoples: [Person] = Bundle.main.decode("data.json")
var body: some View {
NavigationView {
ScrollView(.vertical) {
LazyVStack {
ForEach(peoples, id: \.id) { person in // id replace \.self here
PersonView(name: person.name, age: person.age) // removed uppercase
}
}
.navigationTitle("A list of people")
.navigationViewStyle(DefaultNavigationViewStyle())
.padding()
}
}
}
}

Presenting - Show a view on top in SwiftUI (I don't want to navigate)

Hey There I want to show a custom View in the middle of View I've tried to add ZStack and centered but doesn't work.. here's my code
var body: some View {
VStack(alignment: .leading, spacing: 8) {
headerView.padding().background(Color.white)
ZStack(alignment: .center) {
if(incomeFill_show) {
BudgetAlert(amount: .constant("400"))
}
List() {
VStack {
Section(header: self.getHeaderView()) {
ForEach(categoriesData) { category in
HStack(alignment: .bottom) {
CategoryProgressView(category: category, value: .constant(.random(in: 0.1 ... 1)))
self.valuesText(category: category)
}
}
.colorMultiply(Colors.SharedColors.backgroundColor)
}
}
}
.colorMultiply(Colors.SharedColors.backgroundColor)
.onAppear {UITableView.appearance().separatorStyle = .none}
.onDisappear { UITableView.appearance().separatorStyle = .singleLine }
}
}.background(Colors.SharedColors.backgroundColor)
}
all I want is to show BudgetAlert() with blurred background like this:
I solved it by placing
if(incomeFill_show) {
BudgetAlert(amount: .constant("400"))
}
at the bottom of the List: like this
var body: some View {
VStack(alignment: .leading, spacing: 8) {
headerView.padding().background(Color.white)
ZStack(alignment: .center) {
List() {
VStack {
Section(header: self.getHeaderView()) {
ForEach(categoriesData) { category in
HStack(alignment: .bottom) {
CategoryProgressView(category: category, value: .constant(.random(in: 0.1 ... 1)))
self.valuesText(category: category)
}
}
.colorMultiply(Colors.SharedColors.backgroundColor)
}
}
}
.colorMultiply(Colors.SharedColors.backgroundColor)
.onAppear {UITableView.appearance().separatorStyle = .none}
.onDisappear { UITableView.appearance().separatorStyle = .singleLine }
if(incomeFill_show) {
BudgetAlert(amount: .constant("400"))
}
}
}.background(Colors.SharedColors.backgroundColor)
}
}
for blurred background you can see this code here:
var body: some View {
VStack {
Spacer()
ZStack(alignment: .center) {
RoundedRectangle(cornerRadius: 10).foregroundColor(Color.white)
VStack {
Text("Add your Income").font(Fonts.mediumFont)
HStack(alignment: .center, spacing: 0) {
CustomTextField(placeHolderLabel: "Amount", val: $amount, keyboardType: UIKeyboardType.decimalPad).padding()
HStack {
Button("\(currency.rawValue)"){
self.show_currencyActionsheet = true
}
.font(Fonts.callout)
.foregroundColor(Colors.textFieldFloatingLabel)
.actionSheet(isPresented: self.$show_currencyActionsheet) {self.actionSheetCurrency}
Image(systemName: "chevron.down")
.imageScale(.small)
}.padding()
}.padding([.leading,.trailing])
Button(action: {
self.callBack()
}) {
Text(" Add Income ").font(Fonts.callout).foregroundColor(Color.white)
}
.padding()
.background(Colors.darkGreen)
.clipShape(Capsule())
}
}.frame(minHeight: 150, idealHeight: 182, maxHeight: 200)
.padding()
Spacer()
}.background(VisualEffectView(effect: UIBlurEffect(style: .dark))
.edgesIgnoringSafeArea(.all))
}
struct VisualEffectView: UIViewRepresentable {
var effect: UIVisualEffect?
func makeUIView(context: UIViewRepresentableContext<Self>) -> UIVisualEffectView { UIVisualEffectView() }
func updateUIView(_ uiView: UIVisualEffectView, context: UIViewRepresentableContext<Self>) { uiView.effect = effect }
}
but I prefer to go with faded background