I have a grouped list:
How to change the systemGroupedBackgroundColor of the List?
Here is the code:
struct View2: View {
var array = ["1", "2", "3"]
#State private var selected: String?
var body: some View {
List(array, id:\.self) { value in
Text("\(value) Navigation 1")
.listRowBackground(Color.green)
NavigationLink("", destination: View1())
}.background(Color.red)
.listStyle(GroupedListStyle())
}
}
struct ContentView: View {
var body: some View {
NavigationView {
VStack {
View2()
}
}.navigationBarTitle("Navigation")
}
}
I've tried set the background of the List to Red, but doesn't work. Like below. Thanks!
Insert the following code into View2 and it should work:
init() {
UITableView.appearance().backgroundColor = .red
}
Related
I'm working on Tabview with page style and I want to scroll tabview on button actions. Buttons are added inside NavigationMenu.
NavigationMenu view and NavigationModel(ViewModel) are separated from a parent.
Selection handling is done inside NavigationModel.
On tab page swipe I'm able to see the change in NavigationMenu which is fine.
But if I tap on buttons the tabview page is not swiping. Even I receive change event on method onReceive.
Code:
import SwiftUI
import Combine
final class NavigationModel: ObservableObject {
#Published var selectedItem = ""
#Published var items: [String] = [
"Button 1", "Button 2", "Button 3"
]
}
struct NavigationMenu: View {
#ObservedObject var viewModel: NavigationModel
var body: some View {
HStack {
ForEach(0..<3, id: \.self) { index in
let title = viewModel.items[index]
Button {
viewModel.selectedItem = title
} label: {
Text(title)
.font(.system(.body))
.padding()
.foregroundColor(
viewModel.selectedItem == title ? .white : .black
)
.background(viewModel.selectedItem == title ? .black : .yellow)
}
}
}
}
}
final class TabViewModel: ObservableObject {
var navModel = NavigationModel()
}
struct TabviewWithMenuView: View {
#ObservedObject var viewModel = TabViewModel()
var body: some View {
parentView
}
private var parentView: some View {
VStack(spacing: 0) {
Spacer()
NavigationMenu(viewModel: viewModel.navModel)
pageView
}
.onReceive(viewModel.navModel.$selectedItem) { output in
print("Button tapped:", output)
}
}
private var pageView: some View {
TabView(selection: $viewModel.navModel.selectedItem) {
ForEach(0..<3, id: \.self) { index in
let tag = viewModel.navModel.items[index]
item(tag: tag)
.tag(tag)
}
}
.tabViewStyle(.page(indexDisplayMode: .never))
.transition(.slide)
}
private func item(tag: String) -> some View {
VStack {
Text("PAGE: " + tag)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.red)
}
}
Image:
ObservableObject inside ObservableObject is not observed, we need to observe explicitly the instance which is changed.
A possible solution in this case is to separate PageView and inject navigation view model to it so it would be observed.
Tested with Xcode 13.3 / iOS 15.4
Here is main part:
NavigationMenu(viewModel: viewModel.navModel)
PageView(navModel: viewModel.navModel)
...
struct PageView: View {
#ObservedObject var navModel: NavigationModel
var body: some View {
pageView
}
// ....
}
Test module in project is here
SwiftUI, as of now, does not pass the current accent color to sheets. In the following example, the accent color of the button in the sheet will not be red.
// Some parent view which sets an accent color, can't change this
struct ContentView: View {
var body: some View {
ChildView()
.accentColor(.red)
}
}
// A child view that uses the accent color set by the parent
struct ChildView: View {
#State var showSheet = false
var body: some View {
VStack {
Button("this uses red"){
showSheet = true
}
.sheet(isPresented: $showSheet){
VStack {
Button("this will ignore accent color"){ }
}
}
}
}
}
I'm guessing this is a bug, but I'm looking for a workaround. The problem is, I can't just set .accentColor(.red) or whatever inside of the presented sheet, because my app uses dynamic accent colors depending on a setting. That is to say–I need to be able to pass the accentColor of ChildView on to the sheet, without knowing what it is necessarily.
I'v tried passing .accentColor(.accentColor) to the view in the sheet, which does not work.
Any ideas?
There are a number of ways to do this, the following is just one way:
(macos 11.4, xcode 12.5, target ios 14.5 and macCatalyst 11.3.)
struct ContentView: View {
#State var showSheet = false
#State var accentColor = Color.red
var body: some View {
VStack {
Button("this uses red"){
showSheet = true
}.sheet(isPresented: $showSheet){
TheSheetView(accentColor: $accentColor)
}
}.accentColor(accentColor)
}
}
struct TheSheetView: View {
#Binding var accentColor: Color
var body: some View {
VStack {
Button("this will not ignore accent color"){ }.accentColor(accentColor)
}
}
}
or you could try this:
#main
struct TestErrorApp: App {
var myAccent = MyAccent()
var body: some Scene {
WindowGroup {
ContentView().environmentObject(myAccent)
}
}
}
class MyAccent: ObservableObject {
#Published var color = Color.red
}
struct ContentView: View {
#EnvironmentObject var myAccent: MyAccent // <--- will be available to all child views
#State var showSheet = false
var body: some View {
VStack {
Button("this uses red"){
showSheet = true
}.sheet(isPresented: $showSheet){
TheSheetView().environmentObject(myAccent) // <--- need this for sheets
}
}.accentColor(myAccent.color)
}
}
struct TheSheetView: View {
#EnvironmentObject var myAccent: MyAccent
var body: some View {
VStack {
Button("this will ignore accent color"){ }.accentColor(myAccent.color)
}
}
}
what I am trying to achieve is creating a hierarchical view. I understand that iOS simply doesn't like to use breadcrumbs but I need to navigate from a main view in to deeper subviews. they need to be nested and infinite.
you can see what I've done so far in the code and gif below. As I'm a beginner developer I'm not sure if this is the right way to achieve this kind of structure (infinite sub-views nested inside sub-views). Also when I navigate back in views, added buttons(struct A) disappears. What seems to be the problem?
Thanks in advance!
code in action gif
import SwiftUI
struct A: View, Identifiable {
#EnvironmentObject var documentB: classB
var id: Int
var text: String
var destinationLink: B?
var body: some View {
NavigationLink(destination: self.destinationLink) {
VStack{
Rectangle()
.frame(width: 35, height:25)
.background(Color.red)
Text("\(text)")
}
}
}
}
struct B: View, Identifiable {
#EnvironmentObject var documentB: classB
#State var arrayA: [A] = []
var id: Int
var text: String
var mainText: String = "Placeholder"
var body: some View {
NavigationView {
VStack {
Spacer()
ForEach(arrayA){ item in
item
}
Spacer()
Button(action: {
let newB = B(id:self.documentB.arrayB.count+1, text:"B \(self.documentB.arrayB.count+1)")
self.documentB.arrayB.append(newB)
self.arrayA.append(A(id:self.arrayA.count+1, text:"AA \(self.arrayA.count+1)", destinationLink: newB))
}) {
Text("Add A \(self.arrayA.count), B Count: \(self.documentB.arrayB.count)")
}
}
.navigationBarTitle(text)
}
}
}
class classB: ObservableObject {
#Published var arrayB: [B] = [B(id:1, text:"MainView")]
}
struct ContentView: View {
#ObservedObject var documentB = classB()
var body: some View {
VStack {
documentB.arrayB[0]
}
.environmentObject(documentB)
}
}
You just need to move NavigationView into ContentView, because the only one is needed on one view hierarchy, so
struct ContentView: View {
#ObservedObject var documentB = classB()
var body: some View {
NavigationView { // << move it here from B
VStack {
documentB.arrayB[0]
}
}
.environmentObject(documentB)
}
}
I have a navigation requirement that looks something like this:
Each detail screen can navigation to the next and previous detail screen. At the same time, the "back" button should always go back to the main list (not the previous detail screen).
I'm struggling with how to accomplish this in SwiftUI?
Here is what I have so far:
struct ListView: View {
#State private var currentDetailShown: Int?
#State private var listItems: [Int] = Array(repeating: 0, count: 10)
func goToNext() {
if let idx = self.currentDetailShown {
self.currentDetailShown = min(self.listItems.count - 1, idx + 1)
}
}
func goToPrev() {
if let idx = self.currentDetailShown {
self.currentDetailShown = max(0, idx - 1)
}
}
var body: some View {
List {
ForEach(0..<listItems.count) { index in
NavigationLink(destination: DetailView(goToNext: self.goToNext, goToPrev: self.goToPrev),
tag: index,
selection: self.$currentDetailShown) {
ListItem(score: listItems[index])
}
.isDetailLink(false)
.onTapGesture {
self.currentDetailShown = index
}
}
}
}
}
What happens with this code is that from the first detail view, it'll move to the to the next detail view and then immediately jump back to the list view.
I feel like I'm overthinking this or missing something obvious...
Instead of navigating to each detail from your list, you can navigate to a detailView that can show each detail individually by using a published variable in an observable object. Here is an example
struct MainView: View{
#EnvironmentObject var viewModel: ViewModel
var body: some View{
NavigationView{
VStack{
ForEach(self.viewModel.details, id:\.self){ detail in
NavigationLink(destination: DetailView(detail: self.viewModel.details.firstIndex(of: detail)!).environmentObject(ViewModel())){
Text(detail)
}
}
}
}
}
}
class ViewModel: ObservableObject{
#Published var showingView = 0
#Published var details = ["detail1", "detail2", "detail3", "detail4", "detail5", "detail6"]
}
struct DetailView: View{
#EnvironmentObject var viewModel: ViewModel
#State var detail: Int
var body: some View{
VStack{
IndivisualDetailView(title: viewModel.details[detail])
Button(action: {
self.viewModel.showingView -= 1
}, label: {
Image(systemName: "chevron.left")
})
Button(action: {
self.viewModel.showingView += 1
print(self.viewModel.showingView)
}, label: {
Image(systemName: "chevron.right")
})
}
}
}
struct IndivisualDetailView: View{
#State var title: String
var body: some View{
Text(title)
}
}
I don't often understand when SwiftUI resets the state of a view (i.e. all that is marked with #State). For example, take a look at this minimum example:
import SwiftUI
struct ContentView: View {
#State private var isView1Active = true
let view1 = View1()
let view2 = View2()
var body: some View {
VStack {
if isView1Active {
view1
} else {
view2
}
Button(action: {
self.isView1Active.toggle()
}, label: {
Text("TAP")
})
}
}
}
struct View1: View {
#State private var text = ""
var body: some View {
TextField("View1: type something...", text: $text)
}
}
struct View2: View {
#State private var text = ""
var body: some View {
TextField("View2: type something...", text: $text)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
I'd want the two TextField to keep their content, but if you run this example some weird behaviours occur:
If you run the example on the preview only the View1 TextField content persists:
If you, instead, run the example on the simulator (or on an actual device) neither the first textfield content, nor the second one persist:
So, what's happening here? Is there a way to tell SwiftUI not to reset #State for a view? Thanks.
The issue is that View1 and View2 are being recreated every time isView1Active is changed (because it is using #State which reloads the body of ContentView).
A solution would be to keep the text properties of the TextFields in the ContentView as shown below and use #Binding:
struct ContentView: View {
#State private var isView1Active = true
#State private var view1Text = ""
#State private var view2Text = ""
var body: some View {
VStack {
if isView1Active {
View1(text: $view1Text)
} else {
View2(text: $view2Text)
}
Button(action: {
self.isView1Active.toggle()
}, label: {
Text("TAP")
})
}
}
}
struct View1: View {
#Binding var text: String
var body: some View {
TextField("View1: type something...", text: $text)
}
}
struct View2: View {
#Binding var text: String
var body: some View {
TextField("View2: type something...", text: $text)
}
}
Shown in action:
It view1 and view2 are completely independent and enclosure, like there is no contextmenuor sheet, you may use ZStack and opacity combinations.
var body: some View {
VStack {
ZStack{
if isView1Active {
view1.opacity(1)
view2.opacity(0)
} else {
view1.opacity(0)
view2.opacity(1)
}}
Button(action: {
self.isView1Active.toggle()
}, label: {
Text("TAP")
})
}
}