I am practicing an app in which part of the menu is a horizontal scrollable list, a tiny circle should highlight the current selection. The code is below:
import SwiftUI
struct ContentView: View {
#State var index = 0
var body: some View {
ScrollView(.horizontal, showsIndicators:false) {
HStack(spacing: 30) {
ForEach(0..<topMenu.count, id: \.self){ menu in
TopMenu(menu: menu, index: $index)
}
}
}
}
}
var topMenu = ["Shoes", "Clothing", "By Sports", "By Brand", "By Price"]
struct TopMenu: View {
var menu: Int
#Binding var index: Int
var body: some View {
VStack(spacing: 8) {
Text(topMenu[menu])
.font(.system(size: 15))
.fontWeight(index == menu ? .bold : .none)
.foregroundColor(index == menu ? .black : .gray)
Circle()
.fill(Color.black)
.frame(width: 10, height: 10)
.opacity(index == menu ? 1 : 0)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
But it all seems very static now and I can't seem figure out as to how do I change value of #Binding variable so that it shows current selection of items in topMenu[]?
I assume you meant to change selection on tap, that can be done as below
VStack(spacing: 8) {
Text(topMenu[menu])
.font(.system(size: 15))
.fontWeight(index == menu ? .bold : .none)
.foregroundColor(index == menu ? .black : .gray)
.onTapGesture {
index = menu // << here !!
}
Related
I'm trying to change a view in response to changing a Toggle. I figure out a way to do it as long as I mirror the state value the toggle is using.
Is there a way to do it without creating another variable?
Here's my code. You can see that there are three animations triggered.
import SwiftUI
struct OverlayView: View {
#State var toggle: Bool = false
#State var animatedToggle: Bool = false
var body: some View {
VStack {
Image(systemName: "figure.arms.open")
.resizable()
.scaledToFit()
.foregroundColor(animatedToggle ? .green : .red) // << animated
.opacity(animatedToggle ? 1 : 0.2) // << animated
if animatedToggle { // << animated
Spacer()
}
Toggle("Overlay", isOn: $toggle)
.onChange(of: toggle) { newValue in
withAnimation {
animatedToggle = toggle
}
}
}
}
}
Just use the .animation view modifier
.animation(.default, value: toggle)
Then remove the second variable
struct OverlayView: View {
#State var toggle: Bool = false
var body: some View {
VStack {
Image(systemName: "figure.arms.open")
.resizable()
.scaledToFit()
.foregroundColor(toggle ? .green : .red)
.opacity(toggle ? 1 : 0.2)
if toggle {
Spacer()
}
Toggle("Overlay", isOn: $toggle)
}.animation(.default, value: toggle)
}
}
I have a grid of items. Each item can expand height. I want to autoscroll when the item is expanded so it doesn't overflow the screen.
I was successful with the following code but I had to revert to a hack.
The idea was to detect when the item is overflowing using a Geometry reader on the item's background. Works wonders.
The issue is that when the view is expanded , the geo reader will update after the condition to check if autoscroll should execute is ran by the dispatcher. Hence my ugly hack.
Wonder what is the proper way ?
import SwiftUI
struct BlocksGridView: View {
private var gridItemLayout = [GridItem(.adaptive(minimum: 300, maximum: .infinity), spacing: 20)]
var body: some View {
ZStack{
ScrollView {
ScrollViewReader { value in
LazyVGrid(columns: gridItemLayout, spacing: 20) {
ForEach((0..<20), id: \.self) {
BlockView(cardID: $0,scrollReader: value).id($0)
}
}
}
.padding(20)
}
}
}
}
struct BlockView : View {
var cardID : Int
var scrollReader : ScrollViewProxy
#State private var isOverflowingScreen = false
#State private var expand = false
var body: some View {
ZStack{
Rectangle()
.foregroundColor(isOverflowingScreen ? Color.blue : Color.green)
.frame(height: expand ? 300 : 135)
.clipShape(Rectangle()).cornerRadius(14)
.overlay(Text(cardID.description))
.background(GeometryReader { geo -> Color in
DispatchQueue.main.async {
if geo.frame(in: .global).maxY > UIScreen.main.bounds.maxY {
isOverflowingScreen = true
} else {
isOverflowingScreen = false
}
}
return Color.clear
})
.onTapGesture {
expand.toggle()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { // <-- Hack :(
if isOverflowingScreen {
withAnimation{
scrollReader.scrollTo(cardID)
}
}
}
}
}
}
}
struct BlocksGridView_Previews: PreviewProvider {
static var previews: some View {
BlocksGridView()
}
}
Blue items are overflowing ...
Let me explain, I have a parent view with a SearchBarView, im passing down a focus state binding like this .
SearchBarView(searchText:$object.searchQuery, searching: $object.searching, focused: _searchIsFocused
That works perfectly as #FocusState var searchIsFocused: Bool is defined in parent view passing it down to the SearchBarView (child view ). In parent I can check the change in value and everything ok.
The problem relies when in parent I have the SearchBarView inside .toolbar {} and ToolBarItem(). nothing happens, not change in value of focus, etc. I have my SearchBarView in the top navigation bar and still want to use it there.. but I need to be able to know when it is in focus. if I use inside any VStack or whatever, everything perfectly..
-- EDIT --
providing more code to test
SearchBarView
struct SearchBarView: View {
#Environment(\.colorScheme) var colorScheme
#Binding var searchText: String
#Binding var searching: Bool
#FocusState var focused: Bool
var body: some View {
ZStack {
Rectangle()
.foregroundColor(colorScheme == .dark ? Color("darkSearchColor") : Color.white)
.overlay(
RoundedRectangle(cornerRadius: 13)
.stroke(.black.opacity(0.25), lineWidth: 1)
)
HStack {
Image(systemName: "magnifyingglass").foregroundColor( colorScheme == .dark ? .gray : .gray )
TextField("Search..", text: $searchText )
.focused($focused, equals: true)
.padding(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 20))
.disableAutocorrection(true).onSubmit {
let _ = print("Search textfield Submited by return button")
}
}
.foregroundColor(colorScheme == .dark ? Color("defaultGray") :.gray)
.padding(.leading, 13)
.padding(.trailing, 20).overlay(
HStack {
Spacer()
if searching {
ActivityIndicator().frame(width:15,height:15).aspectRatio(contentMode: .fit).padding(.trailing,15)
}
}
)
.onChange(of: focused) { searchIsFocused in
let _ = print("SEARCH IS FOCUSED VALUE: \(searchIsFocused) ")
}
}
.frame(height: 36)
.cornerRadius(13)
}
}
-- home View Code --
struct HomeView: View {
#Environment(\.colorScheme) var colorScheme
#FocusState var searchIsFocused: Bool
#State var searching:Bool = false
#State var searchQuery: String = ""
var body: some View {
NavigationStack {
GeometryReader { geofull in
ZStack(alignment: .bottom) {
Color("background")//.edgesIgnoringSafeArea([.all])
ScrollView(showsIndicators: false) {
VStack {
// Testing Bar inside VStack.. Here It Works. comment the bar the leave
// the one inside the .toolbar ToolbarItem to test
SearchBarView(searchText:$searchQuery, searching: $searching, focused: _searchIsFocused).padding(0)
}.toolbar {
//MARK: Navbar search field
ToolbarItem(placement:.principal) {
SearchBarView(searchText:$searchQuery, searching: $searching, focused: _searchIsFocused).padding(0)
}
}
.onChange(of: searchIsFocused) { searchIsFocused in
let _ = print("HOME VIEW searchIsFocused VALUE: \(searchIsFocused) ")
}
}
}
}
}
}
}
I would like to make a test with SwiftUI binding.
Aim was changing Picker selection with a button and with an other Picker.
In following code ContentView2's Picker selection will be changed with button in ContentView and Picker in ContentView1.
Button can change ContentView2 picker selection but ContentView1 Picker does not.
I can not find the reason.
You can copy paste code to test.
import SwiftUI
struct ContentView: View {
#State private var index1 = 0
#State private var index2 = 1
var body: some View {
GeometryReader { mainView in
HStack {
Button(action: {
if index2 == 0 {
index2 = 1
} else {
index2 = 0
}
}) {
Text("Button")
}
ContentView1(pickerIndex: $index1)
ContentView2(pickerIndex: $index2)
}
}
}
}
struct ContentView1: View {
#State var pickerData = ["Data1", "Data2"]
#Binding var pickerIndex: Int
#State var pickerIndex2 = 0
var customLabel0: some View{
HStack {
VStack(spacing: 10) {
Text("Picker One")
.foregroundColor(.black)
Text(pickerData[pickerIndex])
.multilineTextAlignment(.center)
}
.foregroundColor(.white)
}
.frame(width: (UIScreen.main.bounds.width - (2.5 * 8 )) * 0.6 * 0.5, height: 100)
.background(Color.gray)
.cornerRadius(5)
}
#State private var testIndex = 1
var body: some View {
Menu {
Picker("", selection: self.$pickerIndex) {
ForEach(0..<pickerData.count, id: \.self) {index in
Text(pickerData[index])
}
}
} label: {
customLabel0
}
.onChange(of: pickerIndex, perform: {_ in
ContentView2(pickerIndex: $pickerIndex)
})
}
}
struct ContentView2: View {
#State var pickerData = ["Data1", "Data2"]
#Binding var pickerIndex: Int
var customLabel0: some View{
HStack {
VStack(spacing: 10) {
Text("Picker Two")
.foregroundColor(.black)
Text(pickerData[pickerIndex])
.multilineTextAlignment(.center)
}
.foregroundColor(.white)
}
.frame(width: (UIScreen.main.bounds.width - (2.5 * 8 )) * 0.6 * 0.5, height: 100)
.background(Color.gray)
.cornerRadius(5)
}
var body: some View {
Menu {
Picker("", selection: $pickerIndex) {
ForEach(0..<pickerData.count, id: \.self) {index in
Text(pickerData[index])
}
}
} label: {
customLabel0
}
.onChange(of: pickerIndex, perform: {_ in
})
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
I am trying to make a struct that shows a menu of radio buttons.
The issue I have is the following: when I press a button, I want the Text(item) View to change color. I'm not sure how to do that, since the Text(item) is encompassed by the button.
import SwiftUI
struct RadioMenu: View {
var items = [String]()
#State var isChecked: Bool = false
#State var selection: String? = nil
var textSize: Int = 20
init(items: [String], textSize: Int) {
self.items = items
self.textSize = textSize
}
var body: some View {
VStack {
ForEach(items, id:\.self) { item in
Button (action: {
self.isChecked = true
self.selection = item
}) {
Text(item)
.font(.system(size: CGFloat(self.textSize), weight: .medium, design: .rounded))
.padding()
.overlay(
RoundedRectangle(cornerRadius: 15)
.stroke(lineWidth: 2)
)}
.padding(.bottom, 10)
}
}
}
}
You can apply this modifier on Text :
.foregroundColor(item == self.selection ? Color.red : Color.black)