Weird behavior matchedGeometryEffect with list - swiftui

Why has only the orange Color a right animation? Green and Red is laying under the list while the animation, but why?
With VStavk there is no problem but with list. Want an animation when switching from list View to Grid View.
struct Colors: Identifiable{
var id = UUID()
var col: Color
}
struct ContentView: View {
#State var on = true
#Namespace var ani
var colors = [Colors(col: .green),Colors(col: .orange),Colors(col: .red)]
var body: some View {
VStack {
if on {
List{
ForEach(colors){col in
col.col
.matchedGeometryEffect(id: "\(col.id)", in: ani)
.animation(.easeIn)
}
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
}
.listStyle(InsetGroupedListStyle())
.frame(height: 400)
} else {
LazyVGrid(columns: [GridItem(.fixed(200)),GridItem(.fixed(200))], content: {
ForEach(colors){col in
col.col
.matchedGeometryEffect(id: "\(col.id)", in: ani)
.animation(.easeIn)
}
})
.frame(height: 400)
}
Button("toggle"){
withAnimation(.easeIn){
on.toggle()
}
}
}
}

Thanks to the comment of #Asperi and this post: Individually modifying child views passed to a container using #ViewBuilder in SwiftUI with the answer of #Tushar Sharma I tried something like this:
import SwiftUI
struct SomeContainerView<Content: View>:View {
var ani: Namespace.ID
var model:[Model] = []
init(namespace: Namespace.ID,model:[Model],#ViewBuilder content: #escaping (Model) -> Content) {
self.content = content
self.model = model
ani = namespace
}
let content: (Model) -> Content
var body: some View {
VStack{
ForEach(model,id:\.id){model in
content(model)
.background(Color.gray.matchedGeometryEffect(id: model.id, in: ani))
}
}
}
}
struct ContentView:View {
#ObservedObject var modelData = Objects()
#Namespace var ani
#State var show = true
var body: some View{
VStack{
Toggle("toggle", isOn: $show.animation())
if show{
SomeContainerView(namespace: ani,model: modelData.myObj){ data in
HStack{
Text("\(data.name)")
data.color.frame(width: 100,height : 100)
}
}
}else{
LazyVGrid(columns: [GridItem(.fixed(110)),GridItem(.fixed(110))],spacing: 10){
ForEach(modelData.myObj){model in
Text("\(model.name)")
.frame(width: 100,height: 100)
.background(Color.gray.matchedGeometryEffect(id: model.id, in: ani))
}
}
}
}
}
}
struct Model: Identifiable{
var id = UUID().uuidString
var name:String
var color:Color
init(name:String,color:Color) {
self.name = name
self.color = color
}
}
class Objects:ObservableObject{
#Published var myObj:[Model] = []
init() {
initModel()
}
func initModel(){
let model = Model(name: "Jack", color: .green)
let model1 = Model(name: "hey Jack", color: .red)
let model2 = Model(name: "hey billy", color: .red)
myObj.append(model)
myObj.append(model1)
myObj.append(model2)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

Related

How I can add a drawing function in SwiftUI?

I can't draw. Unfortunately I don't know where the problem is, colour selection works and the canva is also created, only the drawing doesn't work. I have also checked that the size of the canva is correct and that the path is defined correctly. here is my code:
//
import SwiftUI
struct ContentView: View {
#State private var lines: [Line] = []
#State private var strokeColor: Color = Color.black
var body: some View {
ZStack {
Color.white
.edgesIgnoringSafeArea(.all)
VStack {
Canvas(lines: $lines, strokeColor: $strokeColor)
HStack {
Button(action: clearDrawing) {
Text("Clear")
}
ColorPicker("Stroke Color", selection: $strokeColor)
}
.padding()
}
}
}
func clearDrawing() {
lines.removeAll()
}
}
//
Here I have created the Canva and the button to delete the drawing
//
struct Canvas: View {
#Binding var lines: [Line]
#Binding var strokeColor: Color
var body: some View {
GeometryReader { geometry in
Path { path in
for line in self.lines {
path.move(to: line.points[0])
path.addLines(line.points)
}
}
.stroke(self.strokeColor, lineWidth: 3)
.frame(width: geometry.size.width, height: geometry.size.height)
.gesture(
DragGesture(minimumDistance: 0.1)
.onChanged({ value in
var lastLine = self.lines.last!
let currentPoint = value.location
lastLine.points.append(currentPoint)
})
.onEnded({ value in
self.lines.append(Line())
})
)
}
}
}
struct Line: Identifiable {
var id: UUID = UUID()
var points: [CGPoint] = []
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
//
And here I have made it so that you should actually draw by touch, and that there is a colour selection

How to update Navigation Link subscript with an array of views inside a ForEach Loop?

I am utilizing a search bar from a Kavsoft Tutorial here: "https://www.youtube.com/watch?v=nuag1PILxCA&t=14s", I'm wondering on how to add navigation links to each of the items, I decided on embedding the itemView inside a navigation link with an array of views to loop through but it seems that it doesn't accept the index as a parameter giving "Cannot convert value of type 'item' to expected argument type 'Int'", instead I incremented the subscript on appear in the navigation link, although that updates the variable, but it doesn't seem to work for the different views themselves only navigating to the first view.
I've linked all the code needed to reproduce the problem but due to my incredibly limited experience in reproducing the problem in as less code as possible, I am not able to do so. Below the main issue of concern is the block starting from the VStack. Starting the program can be done by just adding Search_Bar() to content view body.
struct Home: View {
let views : [AnyView] = [ AnyView(untitled_Skull()), AnyView(dogs()), AnyView(cats()) ]
#Binding var filteredItems : [item]
var body: some View {
ScrollView(.vertical, showsIndicators: false) {
var i = 0
VStack(spacing: 15){
ForEach(filteredItems){index in
NavigationLink(destination: views[i]
) {
itemView(item: index)
}.onAppear() {
i = i + 1
}
}
}
.padding()
}
}
}
func add(value: Int) -> Int {
let value = value + 1
return value
}
struct itemView: View {
var item: item
#State var show = false
var body: some View {
HStack(spacing: 15){
VStack {
let colorArray: [Color] = [.yellowLichtenstien, .redHaring, .orangeBasquiat, .pinkWarhol]
HStack {
Text(item.name)
.foregroundColor(.white)
.bold()
.padding(.leading)
Spacer()
}
HStack {
Text(item.subText)
.bold()
.foregroundColor (.white)
.font(.subheadline)
.padding(.leading)
Circle()
.frame(width: 5, height: 5)
.foregroundColor(colorArray[item.color])
Text(item.subText2)
.bold()
.foregroundColor (.white)
.font(.subheadline)
Spacer()
}
Spacer()
}
}
.padding(.horizontal)
}
}
struct item: Identifiable {
var id = UUID().uuidString
// both Image And Name Are Same....
var name: String
// since all Are Apple Native Apps...
var color: Int
var subText: String
var subText2: String
}
var searchItems = [
item(name: "Untitled (Skull)", color: 0, subText: "1983", subText2: "yay"),
item(name: "Dogs", color: 1, subText: "1972", subText2: "wow"),
item(name: "Cats", color: 2, subText: "1968", subText2: "oof")
]
struct Search_Bar: View {
#State var filteredItems = searchItems
var body: some View {
CustomNavigationView(view: AnyView(Home(filteredItems: $filteredItems)), placeHolder: "Museums, Art or anything else.", largeTitle: true, title: "Search",
onSearch: { (txt) in
if txt != ""{
self.filteredItems = searchItems.filter{$0.name.lowercased().contains(txt.lowercased())}
}
else{
self.filteredItems = searchItems
}
}, onCancel: {
// Do Your Own Code When Search And Canceled....
self.filteredItems = searchItems
})
.ignoresSafeArea()
}
}
struct Search_Bar_Previews: PreviewProvider {
static var previews: some View {
Search_Bar()
}
}
import SwiftUI
struct CustomNavigationView: UIViewControllerRepresentable {
func makeCoordinator() -> Coordinator {
return CustomNavigationView.Coordinator(parent: self)
}
// Just Change Your View That Requires Search Bar...
var view: AnyView
// Ease Of Use.....
var largeTitle: Bool
var title: String
var placeHolder: String
// onSearch And OnCancel Closures....
var onSearch: (String)->()
var onCancel: ()->()
// requre closure on Call...
init(view: AnyView,placeHolder: String? = "Search",largeTitle: Bool? = true,title: String,onSearch: #escaping (String)->(),onCancel: #escaping ()->()) {
self.title = title
self.largeTitle = largeTitle!
self.placeHolder = placeHolder!
self.view = view
self.onSearch = onSearch
self.onCancel = onCancel
}
// Integrating UIKit Navigation Controller With SwiftUI View...
func makeUIViewController(context: Context) -> UINavigationController {
// requires SwiftUI View...
let childView = UIHostingController(rootView: view)
let controller = UINavigationController(rootViewController: childView)
// Nav Bar Data...
controller.navigationBar.topItem?.title = title
controller.navigationBar.prefersLargeTitles = largeTitle
// search Bar....
let searchController = UISearchController()
searchController.searchBar.placeholder = placeHolder
// setting delegate...
searchController.searchBar.delegate = context.coordinator
// setting Search Bar In NavBar...
// disabling hide on scroll...
// disabling dim bg..
searchController.obscuresBackgroundDuringPresentation = false
controller.navigationBar.topItem?.hidesSearchBarWhenScrolling = false
controller.navigationBar.topItem?.searchController = searchController
return controller
}
func updateUIViewController(_ uiViewController: UINavigationController, context: Context) {
// Updating Real Time...
uiViewController.navigationBar.topItem?.title = title
uiViewController.navigationBar.topItem?.searchController?.searchBar.placeholder = placeHolder
uiViewController.navigationBar.prefersLargeTitles = largeTitle
}
// search Bar Delegate...
class Coordinator: NSObject,UISearchBarDelegate{
var parent: CustomNavigationView
init(parent: CustomNavigationView) {
self.parent = parent
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
// when text changes....
self.parent.onSearch(searchText)
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
// when cancel button is clicked...
self.parent.onCancel()
}
}
}
Letting the random view below for the array being for example:
import SwiftUI
struct cats: View {
var body: some View {
Text("cats") //replacing this with dogs or untitled skull as an example.
}
}
struct cats_Previews: PreviewProvider {
static var previews: some View {
cats()
}
}
You can use ForEach getting the item and its index in the closure :
ForEach(Array(filteredItems.enumerated()), id: \.1.id) { index, item in
NavigationLink(destination: views[index]){
Text(item.name)
}
}
For example :
struct ListItem: Identifiable {
let id = UUID()
let name: String
}
struct SwiftUIView17: View {
#State private var filteredItems = ["John", "Bob", "Maria"].map(ListItem.init)
let views = [AnyView(Text("John destination")), AnyView(Text("Bob destination")), AnyView(Text("Maria destination"))]
var body: some View {
ScrollView {
ForEach(Array(filteredItems.enumerated()), id: \.1.id) { index, item in
NavigationLink(destination: views[index]){
Text(item.name)
}
}
}
}
}
But it would be better not to use AnyView but a ViewBuilder :
struct SwiftUIView17: View {
#State private var filteredItems = ["John", "Bob", "Maria"].map(ListItem.init)
#ViewBuilder func destination(for itemIndex: Int) -> some View {
switch itemIndex {
case 0: Text("John destination")
case 1: Text("Bob destination").foregroundColor(.red)
case 2: Rectangle()
default: Text("error")
}
}
var body: some View {
ScrollView {
ForEach(Array(filteredItems.enumerated()), id: \.1.id) { index, item in
NavigationLink(destination: destination(for: index)){
Text(item.name)
}
}
}
}
}

How to Add Color beside text into the list in swiftUI?(Data Flow)

I try to get( text & color ) from user and add them to the list in SwiftUI I already can pass text data but unfortunately for color I can't while they should be the same, below there is an image of app.To work we should provide a Binding for PreAddTextField .Thanks for your help
here is my Code :
import SwiftUI
struct AddListView: View {
#Binding var showAddListView : Bool
#ObservedObject var appState : AppState
#StateObject private var viewModel = AddListViewViewModel()
var body: some View {
ZStack {
Title(addItem: { viewModel.textItemsToAdd.append(.init(text: "", color: .purple)) })
VStack {
ScrollView {
ForEach(viewModel.textItemsToAdd, id: \.id) { item in //note this is id:
\.id and not \.self
PreAddTextField(textInTextField: viewModel.bindingForId(id: item.id), colorPickerColor: <#Binding<Color>#>)
}
}
}
.padding()
.offset(y: 40)
Buttons(showAddListView: $showAddListView, save: {
viewModel.saveToAppState(appState: appState)
})
}
.frame(width: 300, height: 200)
.background(Color.white)
.shadow(color: Color.black.opacity(0.3), radius: 10, x: 0, y: 10)
}
}
struct SwiftUIView_Previews: PreviewProvider {
static var previews: some View {
AddListView(showAddListView: .constant(false),appState: AppState())
}
}
struct PreAddTextField: View {
#Binding var textInTextField : String
#Binding var colorPickerColor : Color
var body: some View {
HStack {
TextField("Enter text", text: $textInTextField)
ColorPicker("", selection: $colorPickerColor)
}
}
}
struct Buttons: View {
#Binding var showAddListView : Bool
var save : () -> Void
var body: some View {
VStack {
HStack(spacing:100) {
Button(action: {
showAddListView = false}) {
Text("Cancel")
}
Button(action: {
showAddListView = false
// What should happen here to add Text to List???
save()
}) {
Text("Add")
}
}
}
.offset(y: 70)
}
}
struct Title: View {
var addItem : () -> Void
var body: some View {
VStack {
HStack {
Text("Add Text to list")
.font(.title2)
Spacer()
Button(action: {
addItem()
}) {
Image(systemName: "plus")
.font(.title2)
}
}
.padding()
Spacer()
}
}
}
DataModel :
import SwiftUI
struct Text1 : Identifiable , Hashable{
var id = UUID()
var text : String
var color : Color
}
class AppState : ObservableObject {
#Published var textData : [Text1] = [.init(text: "Item 1", color: .purple),.init(text: "Item 2", color: .purple)]
}
class AddListViewViewModel : ObservableObject {
#Published var textItemsToAdd : [Text1] = [.init(text: "", color: .purple)] //start with one empty item
//save all of the new items -- don't save anything that is empty
func saveToAppState(appState: AppState) {
appState.textData.append(contentsOf: textItemsToAdd.filter { !$0.text.isEmpty })
}
//these Bindings get used for the TextFields -- they're attached to the item IDs
func bindingForId(id: UUID) -> Binding<String> {
.init { () -> String in
self.textItemsToAdd.first(where: { $0.id == id })?.text ?? ""
} set: { (newValue) in
self.textItemsToAdd = self.textItemsToAdd.map {
guard $0.id == id else {
return $0
}
return .init(id: id, text: newValue, color: .purple)
}
}
}
}
and finaly :
import SwiftUI
struct ListView: View {
#StateObject var appState = AppState() //store the AppState here
#State private var showAddListView = false
var body: some View {
NavigationView {
VStack {
ZStack {
List(appState.textData, id : \.self){ text in
HStack {
Image(systemName: "square")
.foregroundColor(text.color)
Text(text.text)
}
}
if showAddListView {
AddListView(showAddListView: $showAddListView, appState: appState)
.offset(y:-100)
}
}
}
.navigationTitle("List")
.navigationBarItems(trailing:
Button(action: {showAddListView = true}) {
Image(systemName: "plus")
.font(.title2)
}
)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ListView()
}
}
Instead of using a Binding for just the String, you could create a Binding for the entire Text1 item:
class AddListViewViewModel : ObservableObject {
#Published var textItemsToAdd : [Text1] = [.init(text: "", color: .purple)]
func saveToAppState(appState: AppState) {
appState.textData.append(contentsOf: textItemsToAdd.filter { !$0.text.isEmpty })
}
func bindingForId(id: UUID) -> Binding<Text1> { //now returns a Text1
.init { () -> Text1 in
self.textItemsToAdd.first(where: { $0.id == id }) ?? Text1(text: "", color: .clear)
} set: { (newValue) in
self.textItemsToAdd = self.textItemsToAdd.map {
guard $0.id == id else {
return $0
}
return newValue
}
}
}
}
struct PreAddTextField: View {
#Binding var item : Text1
var body: some View {
HStack {
TextField("Enter text", text: $item.text) //gets the text property of the binding
ColorPicker("", selection: $item.color) //gets the color property
}
}
}
struct AddListView: View {
#Binding var showAddListView : Bool
#ObservedObject var appState : AppState
#StateObject private var viewModel = AddListViewViewModel()
var body: some View {
ZStack {
Title(addItem: { viewModel.textItemsToAdd.append(.init(text: "", color: .purple)) })
VStack {
ScrollView {
ForEach(viewModel.textItemsToAdd, id: \.id) { item in
PreAddTextField(item: viewModel.bindingForId(id: item.id)) //parameter is changed here
}
}
}
.padding()
.offset(y: 40)
Buttons(showAddListView: $showAddListView, save: {
viewModel.saveToAppState(appState: appState)
})
}
.frame(width: 300, height: 200)
.background(Color.white)
.shadow(color: Color.black.opacity(0.3), radius: 10, x: 0, y: 10)
}
}

How to Add multi text into the list in SwiftUI?(Data Flow)

I'm trying to build an demo app by swiftUI that get multi text from user and add them to the list, below , there is an image of app every time user press plus button the AddListView show to the user and there user can add multi text to the List.I have a problem to add them to the list by new switUI data Flow I don't know how to pass data.(I comment more information)
Thanks 🙏
here is my code for AddListView:
import SwiftUI
struct AddListView: View {
#State var numberOfTextFiled = 1
#Binding var showAddListView : Bool
var body: some View {
ZStack {
Title(numberOfTextFiled: $numberOfTextFiled)
VStack {
ScrollView {
ForEach(0 ..< numberOfTextFiled, id: \.self) { item in
PreAddTextField()
}
}
}
.padding()
.offset(y: 40)
Buttons(showAddListView: $showAddListView)
}
.frame(width: 300, height: 200)
.background(Color.white)
.shadow(color: Color.black.opacity(0.3), radius: 10, x: 0, y: 10)
}
}
struct SwiftUIView_Previews: PreviewProvider {
static var previews: some View {
AddListView(showAddListView: .constant(false))
}
}
struct PreAddTextField: View {
// I made this standalone struct and use #State to every TextField text be independent
// if i use #Binding to pass data all Texfield have the same text value
#State var textInTextField = ""
var body: some View {
VStack {
TextField("Enter text", text: $textInTextField)
}
}
}
struct Buttons: View {
#Binding var showAddListView : Bool
var body: some View {
VStack {
HStack(spacing:100) {
Button(action: {
showAddListView = false}) {
Text("Cancel")
}
Button(action: {
showAddListView = false
// What should happen here to add Text to List???
}) {
Text("Add")
}
}
}
.offset(y: 70)
}
}
struct Title: View {
#Binding var numberOfTextFiled : Int
var body: some View {
VStack {
HStack {
Text("Add Text to list")
.font(.title2)
Spacer()
Button(action: {
numberOfTextFiled += 1
}) {
Image(systemName: "plus")
.font(.title2)
}
}
.padding()
Spacer()
}
}
}
and for DataModel:
import SwiftUI
struct Text1 : Identifiable , Hashable{
var id = UUID()
var text : String
}
var textData = [
Text1(text: "SwiftUI"),
Text1(text: "Data flow?"),
]
and finally:
import SwiftUI
struct ListView: View {
#State var showAddListView = false
var body: some View {
NavigationView {
VStack {
ZStack {
List(textData, id : \.self){ text in
Text(text.text)
}
if showAddListView {
AddListView(showAddListView: $showAddListView)
.offset(y:-100)
}
}
}
.navigationTitle("List")
.navigationBarItems(trailing:
Button(action: {showAddListView = true}) {
Image(systemName: "plus")
.font(.title2)
}
)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ListView()
}
}
Because of the multiple-items part of the question, this becomes a lot less trivial. However, using a combination of ObservableObjects and callback functions, definitely doable. Look at the inline comments in the code for explanations about what is going on:
struct Text1 : Identifiable , Hashable{
var id = UUID()
var text : String
}
//Store the items in an ObservableObject instead of just in #State
class AppState : ObservableObject {
#Published var textData : [Text1] = [.init(text: "Item 1"),.init(text: "Item 2")]
}
//This view model stores data about all of the new items that are going to be added
class AddListViewViewModel : ObservableObject {
#Published var textItemsToAdd : [Text1] = [.init(text: "")] //start with one empty item
//save all of the new items -- don't save anything that is empty
func saveToAppState(appState: AppState) {
appState.textData.append(contentsOf: textItemsToAdd.filter { !$0.text.isEmpty })
}
//these Bindings get used for the TextFields -- they're attached to the item IDs
func bindingForId(id: UUID) -> Binding<String> {
.init { () -> String in
self.textItemsToAdd.first(where: { $0.id == id })?.text ?? ""
} set: { (newValue) in
self.textItemsToAdd = self.textItemsToAdd.map {
guard $0.id == id else {
return $0
}
return .init(id: id, text: newValue)
}
}
}
}
struct AddListView: View {
#Binding var showAddListView : Bool
#ObservedObject var appState : AppState
#StateObject private var viewModel = AddListViewViewModel()
var body: some View {
ZStack {
Title(addItem: { viewModel.textItemsToAdd.append(.init(text: "")) })
VStack {
ScrollView {
ForEach(viewModel.textItemsToAdd, id: \.id) { item in //note this is id: \.id and not \.self
PreAddTextField(textInTextField: viewModel.bindingForId(id: item.id))
}
}
}
.padding()
.offset(y: 40)
Buttons(showAddListView: $showAddListView, save: {
viewModel.saveToAppState(appState: appState)
})
}
.frame(width: 300, height: 200)
.background(Color.white)
.shadow(color: Color.black.opacity(0.3), radius: 10, x: 0, y: 10)
}
}
struct PreAddTextField: View {
#Binding var textInTextField : String //this takes a binding to the view model now
var body: some View {
VStack {
TextField("Enter text", text: $textInTextField)
}
}
}
struct Buttons: View {
#Binding var showAddListView : Bool
var save : () -> Void //callback function for what happens when "Add" gets pressed
var body: some View {
VStack {
HStack(spacing:100) {
Button(action: {
showAddListView = false}) {
Text("Cancel")
}
Button(action: {
showAddListView = false
save()
}) {
Text("Add")
}
}
}
.offset(y: 70)
}
}
struct Title: View {
var addItem : () -> Void //callback function for what happens when the plus button is hit
var body: some View {
VStack {
HStack {
Text("Add Text to list")
.font(.title2)
Spacer()
Button(action: {
addItem()
}) {
Image(systemName: "plus")
.font(.title2)
}
}
.padding()
Spacer()
}
}
}
struct ListView: View {
#StateObject var appState = AppState() //store the AppState here
#State private var showAddListView = false
var body: some View {
NavigationView {
VStack {
ZStack {
List(appState.textData, id : \.self){ text in
Text(text.text)
}
if showAddListView {
AddListView(showAddListView: $showAddListView, appState: appState)
.offset(y:-100)
}
}
}
.navigationTitle("List")
.navigationBarItems(trailing:
Button(action: {showAddListView = true}) {
Image(systemName: "plus")
.font(.title2)
}
)
}
}
}

SwiftUI Segmented Control selected segment text animation on view refresh

I am experiencing the following animation of the text in the selected segment of Segmented Controls when the View is refreshed after changing some other data in the View:
Is this a bug/feature or is there a way to eliminate this behaviour?
This is the code to reproduce the effect:
import SwiftUI
struct ContentView: View {
let colorNames1 = ["Red", "Green", "Blue"]
#State private var color1 = 0
let colorNames2 = ["Yellow", "Purple", "Orange"]
#State private var color2 = 0
var body: some View {
VStack {
VStack {
Picker(selection: $color1, label: Text("Color")) {
ForEach(0..<3, id: \.self) { index in
Text(self.colorNames1[index]).tag(index)
}
}.pickerStyle(SegmentedPickerStyle())
Text("Color 1: \(color1)")
}
.padding()
VStack {
Picker(selection: $color2, label: Text("Color")) {
ForEach(0..<3, id: \.self) { index in
Text(self.colorNames2[index]).tag(index)
}
}.pickerStyle(SegmentedPickerStyle())
Text("Color 2: \(color2)")
}
.padding()
}
}
}
This was run under iOS 13.4 / Xcode 11.4
rearrange you code base ... (this helps SwiftUI to "refresh" only necessary Views)
import SwiftUI
struct ContentView: View {
let colorNames1 = ["Red", "Green", "Blue"]
#State private var color1 = 0
let colorNames2 = ["Yellow", "Purple", "Orange"]
#State private var color2 = 0
var body: some View {
VStack {
MyPicker(colorNames: colorNames1, color: $color1)
.padding()
MyPicker(colorNames: colorNames2, color: $color2)
.padding()
}
}
}
struct MyPicker: View {
let colorNames: [String]
#Binding var color: Int
var body: some View {
VStack {
Picker(selection: $color, label: Text("Color")) {
ForEach(0..<colorNames.count) { index in
Text(self.colorNames[index]).tag(index)
}
}.pickerStyle(SegmentedPickerStyle())
Text("Color 1: \(color)")
}
}
}
struct ContetView_Preview: PreviewProvider {
static var previews: some View {
ContentView()
}
}
result
I created a custom SegmentControl to solve this problem:
import SwiftUI
struct MyTextPreferenceKey: PreferenceKey {
typealias Value = [MyTextPreferenceData]
static var defaultValue: [MyTextPreferenceData] = []
static func reduce(value: inout [MyTextPreferenceData], nextValue: () -> [MyTextPreferenceData]) {
value.append(contentsOf: nextValue())
}
}
struct MyTextPreferenceData: Equatable {
let viewIndex: Int
let rect: CGRect
}
struct SegmentedControl : View {
#Binding var selectedIndex: Int
#Binding var rects: [CGRect]
#Binding var titles: [String]
var body: some View {
ZStack(alignment: .topLeading) {
SelectedView()
.frame(width: rects[selectedIndex].size.width - 4, height: rects[selectedIndex].size.height - 4)
.offset(x: rects[selectedIndex].minX + 2, y: rects[selectedIndex].minY + 2)
.animation(.easeInOut(duration: 0.5))
VStack {
self.addTitles()
}.onPreferenceChange(MyTextPreferenceKey.self) { preferences in
for p in preferences {
self.rects[p.viewIndex] = p.rect
}
}
}.background(Color(.red)).clipShape(Capsule()).coordinateSpace(name: "CustomSegmentedControl")
}
func totalSize() -> CGSize {
var totalSize: CGSize = .zero
for rect in rects {
totalSize.width += rect.width
totalSize.height = rect.height
}
return totalSize
}
func addTitles() -> some View {
HStack(alignment: .center, spacing: 8, content: {
ForEach(0..<titles.count) { index in
return SegmentView(selectedIndex: self.$selectedIndex, label: self.titles[index], index: index, isSelected: self.segmentIsSelected(selectedIndex: self.selectedIndex, segmentIndex: index))
}
})
}
func segmentIsSelected(selectedIndex: Int, segmentIndex: Int) -> Binding<Bool> {
return Binding(get: {
return selectedIndex == segmentIndex
}) { (value) in }
}
}
struct SegmentView: View {
#Binding var selectedIndex: Int
let label: String
let index: Int
#Binding var isSelected: Bool
var body: some View {
Text(label)
.padding(.vertical, 6)
.padding(.horizontal, 10)
.foregroundColor(Color(.label))
.background(MyPreferenceViewSetter(index: index)).onTapGesture {
self.selectedIndex = self.index
}
}
}
struct MyPreferenceViewSetter: View {
let index: Int
var body: some View {
GeometryReader { geometry in
Rectangle()
.fill(Color.clear)
.preference(key: MyTextPreferenceKey.self,
value: [MyTextPreferenceData(viewIndex: self.index, rect: geometry.frame(in: .named("CustomSegmentedControl")))])
}
}
}
struct SelectedView: View {
var body: some View {
Capsule()
.fill(Color(.systemBackground))
.edgesIgnoringSafeArea(.horizontal)
}
}
result