Hi the swiftui code below shows a table with a title at the top, what I can't do is change the background of the table to gray and align everything at the top, how can I do? below is the current swiftui code, thanks
SwiftUI Code:
//
// PresenzeUiView.swift
// clientipad
//
// Created by riccardo on 06/02/23.
//
import SwiftUI
struct UserTable: Identifiable {
let id: Int
var nome: String
var cognome: String
var orarioEntrata: String
var orarioUscita: String
var avvisi: String
}
struct PresenzeUiView: View {
#State private var users = [
UserTable(id: 1, nome: "Michele", cognome: "Rossi", orarioEntrata: "08:00", orarioUscita: "20:00", avvisi: "prova"),
UserTable(id: 2, nome: "Michele", cognome: "Rossi", orarioEntrata: "08:00", orarioUscita: "20:00", avvisi: "prova"), UserTable(id: 1, nome: "Michele", cognome: "Rossi", orarioEntrata: "08:00", orarioUscita: "20:00", avvisi: "prova"),
UserTable(id: 3, nome: "Michele", cognome: "Rossi", orarioEntrata: "08:00", orarioUscita: "20:00", avvisi: "prova"),
]
var body: some View {
VStack {
Text("Storico Presenze").foregroundColor(.black)
Table(users) {
TableColumn("Cognome", value: \.cognome)
TableColumn("Nome") { user in
Text(String(user.nome))
}
TableColumn("Orario Entrata") { user in
Text(String(user.orarioEntrata))
}
TableColumn("Orario Uscita") { user in
Text(String(user.orarioUscita))
}
TableColumn("Avvisi") { user in
Text(String(user.avvisi))
}
}.frame(width: UIScreen.screenWidth - 400, height: UIScreen.screenHeight - 400)
}.background(Color.white).frame(width: UIScreen.screenWidth - 300, height: UIScreen.screenHeight)
}
}
struct PresenzeUiView_Previews: PreviewProvider {
static var previews: some View {
PresenzeUiView()
}
}
To make the background of the VStack visible, you just need to hide the background of the Table, using:
.scrollContentBackground(.hidden)
e.g.
var body: some View {
VStack {
Text("Storico Presenze").foregroundColor(.black)
Table(users) {
TableColumn("Cognome", value: \.cognome)
TableColumn("Nome") { user in
Text(String(user.nome))
}
TableColumn("Orario Entrata") { user in
Text(String(user.orarioEntrata))
}
TableColumn("Orario Uscita") { user in
Text(String(user.orarioUscita))
}
TableColumn("Avvisi") { user in
Text(String(user.avvisi))
}
}
.scrollContentBackground(.hidden)
.padding()
}
.background(.gray)
.padding(150)
}
Also, try using SwiftUI .padding to size your views relative to a container, rather than resorting to UIKit's UIScreen.etc
Related
I previously asked a question about how to link an array of TabButtons to .tag() here: .tag() in TabView in SwiftUI challenge, but now I want to customize each TabView to have different information and not just the one line of text that reads from the enum cases that was created. In the below code I added additional views that I would like to populate with each TabView. I want to be able to insert additional tabs/views in the future. I've created the custom views at the bottom, but not sure how to link with the enums and then use a ForEach in TabView.
import SwiftUI
struct ContentView: View {
var body: some View {
CustomTabView()
}
}
struct CustomTabView: View {
#State var selectedTab = ViewSelect.HomeView // This is an enum defined below.
#State var viewData : AllViewData!
var body: some View {
ZStack(alignment: Alignment(horizontal: .center, vertical: .bottom)) {
TabView(selection: $selectedTab) {
ForEach(ViewSelect.allCases, id: \.self) { view in // This iterates through all of the enum cases.
ViewsCardView(viewData: view.allViewData)
.tag(view.rawValue) // by having the tag be the enum's raw value,
// you can always compare enum to enum.
}
}
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
.ignoresSafeArea(.all, edges: .bottom)
ScrollView(.horizontal, showsIndicators: false, content: {
HStack {
ForEach(ViewSelect.allCases ,id: \.self){viewSelect in
TabButton(viewSelect: viewSelect, selectedTab: $selectedTab)
}
}
})
.padding(.horizontal, 25)
.padding(.vertical, 5)
.background(Color.white)
.clipShape(Capsule())
.shadow(color: Color.black.opacity(0.15), radius: 5, x: 5, y: 5)
.shadow(color: Color.black.opacity(0.15), radius: 5, x: -5, y: -5)
.padding(.horizontal)
}
.background(Color.black.opacity(0.05).ignoresSafeArea(.all, edges: .all))
}
}
struct TabButton: View {
let viewSelect: ViewSelect
#Binding var selectedTab: ViewSelect
var body: some View {
Button(action: {selectedTab = viewSelect}) {
Image(systemName: viewSelect.tabIcon)
.renderingMode(.template)
// this compares the selection to the button's associated enum.
// The enum provides the image name to the button,
// but we are always dealing with a case of the enum.
.opacity(selectedTab == viewSelect ? (1) : (0.5))
.padding()
}
}
}
struct ViewsCardView: View {
#State var viewData: AllViewData
var body: some View {
VStack {
Text(viewData.name)
}
}
}
struct AllViewData : Identifiable, Hashable {
var id = UUID().uuidString
var name : String
var text : Int
}
public enum ViewSelect: String, CaseIterable {
case HomeView, EnvelopeView, FolderView, SettingsView
var tabIcon: String {
switch self {
case .HomeView:
return "house"
case .EnvelopeView:
return "envelope"
case .FolderView:
return "folder"
case .SettingsView:
return "gear"
}
}
var allViewData: AllViewData {
return AllViewData(name: self.rawValue, text: self.hashValue)
}
}
struct DataForViews : Identifiable {
var id = UUID().uuidString
var someText : SomeText
var someImage : SomeImage
var someData : String
var moreText : String
enum SomeText : String, CaseIterable {
case friends
case enemies
case neutral
case both
}
enum SomeImage : String, CaseIterable {
case friends = "person.3"
case enemies = "person.fill.xmark"
case neutral = "person"
case both = "person.circle"
}
}
struct HomeView: View {
#State var viewData = DataForViews(someText: .friends, someImage: .friends, someData: "Some Data", moreText: "More Text")
var body: some View {
VStack {
Text(viewData.someText.rawValue)
Image(systemName: viewData.someImage.rawValue)
.resizable()
.frame(width: 40, height: 40)
Text(viewData.someData)
Text(viewData.moreText)
}
}
}
struct EnvelopeView: View {
#State var viewData = DataForViews(someText: .enemies, someImage: .enemies, someData: "Some Data", moreText: "More Text")
var body: some View {
VStack {
Text(viewData.someText.rawValue)
Image(systemName: viewData.someImage.rawValue)
.resizable()
.frame(width: 40, height: 40)
Text(viewData.moreText)
}
}
}
struct FolderView: View {
#State var viewData = DataForViews(someText: .neutral, someImage: .neutral, someData: "Some Data", moreText: "More Text")
var body: some View {
VStack {
Text(viewData.someText.rawValue)
Image(systemName: viewData.someImage.rawValue)
.resizable()
.frame(width: 40, height: 40)
Text(viewData.someData)
Text(viewData.moreText)
}
}
}
struct SettingsView: View {
#State var viewData = DataForViews(someText: .both, someImage: .both, someData: "Some Data", moreText: "More Text")
var body: some View {
VStack {
Text(viewData.someText.rawValue)
Image(systemName: viewData.someImage.rawValue)
.resizable()
.frame(width: 40, height: 40)
Text(viewData.someData)
}
}
}
I think I figured this out. It took me a lot of tinkering and I came here to stack overflow to try and get further clarity. But the below solution should work. I created a variable in the HomeView() that linked the ViewSelect as an enum, then I initialized the different options with an array in the DataForViews struct and linked the selection to the ViewSelect enum which is tied to the tabItems.
Then in the ContentView, I followed the same pattern except used the ForEach loop on the HomeView() and for the .tag() it is tied to the selection within the DataForViews.
struct CustomTabView: View {
#State var selectedTab = ViewSelect.HomeView // This is an enum defined below.
#State var viewData : AllViewData!
var body: some View {
ZStack(alignment: Alignment(horizontal: .center, vertical: .bottom)) {
TabView(selection: $selectedTab) {
ForEach(viewDataStuff) { view in
HomeView(viewData: view)
.tag(view.selection)
}
struct DataForViews : Identifiable, Hashable {
var id = UUID().uuidString
var someText : SomeText
var someImage : SomeImage
var someData : String
var moreText : String
var selection : ViewSelect
enum SomeText : String, CaseIterable {
case friends
case enemies
case neutral
case both
}
enum SomeImage : String, CaseIterable {
case friends = "person.3"
case enemies = "person.fill.xmark"
case neutral = "person"
case both = "person.circle"
}
}
var viewDataStuff = [
DataForViews(someText: .friends, someImage: .friends, someData: "Text", moreText: "Text", selection: .HomeView),
DataForViews(someText: .enemies, someImage: .enemies, someData: "Text", moreText: "Text", selection: .EnvelopeView),
DataForViews(someText: .neutral, someImage: .neutral, someData: "Text", moreText: "Text", selection: .FolderView),
DataForViews(someText: .both, someImage: .both, someData: "Text", moreText: "Text", selection: .SettingsView),
]
struct HomeView: View {
#State var viewData : DataForViews
var body: some View {
VStack {
Text(viewData.someText.rawValue)
Image(systemName: viewData.someImage.rawValue)
.resizable()
.frame(width: 40, height: 40)
Text(viewData.someData)
Text(viewData.moreText)
}
}
}
'''
I have Row View:
struct TestRow: View {
var rowText: String
var body: some View {
HStack {
Text(rowText)
Spacer()
Image(systemName: "heart.circle.fill")
}
.listRowBackground(Color.red)
}
.listRowbackground work correctly, but if I add contextMenu, background changing to default (white) and don't changing from contextMenu :
struct TestRow: View {
#State var rowColor: Color = Color.mint
var rowText: String
var body: some View {
HStack {
Text(rowText)
Spacer()
Image(systemName: "heart.circle.fill")
}
// .background(rowColor) -> this code working with contextMenu
.listRowBackground(rowColor)
.contextMenu {
VStack {
Button {
//action code
rowColor = Color.red
} label: {
MenuButton(colorOrb: "red_circle", buttonText: "High")
}
Button {
//action code
rowColor = .yellow
} label: {
MenuButton(colorOrb: "yellow_circle", buttonText: "Medium")
}
Button {
//action code
rowColor = .green
} label: {
MenuButton(colorOrb: "green_circle", buttonText: "Normal")
}
}
}
}
}
If I use .background(rowColor) instead .listRowBackground(rowColor) code work correctly, but I need colorise all row, not HStack only.
struct Item: Identifiable {
var id: Int
var color: Color
}
struct ContentView2: View {
#State var items: [Item] = [Item(id: 1, color: .red),
Item(id: 2, color: .yellow),
Item(id: 3, color: .blue)]
var body: some View {
VStack {
List {
ForEach($items) { $item in
TestRow(rowColor: $item.color, rowText: "\(item.id)")
.listRowBackground(item.color)
}
}
}
}
}
struct ContentView2_Previews: PreviewProvider {
static var previews: some View {
ContentView2()
}
}
struct TestRow: View {
#Binding var rowColor: Color
let rowText: String
var body: some View {
HStack {
Text(rowText)
Spacer()
Image(systemName: "heart.circle.fill")
}
// .background(rowColor) -> this code working with contextMenu
.contextMenu {
Button {
//action code
rowColor = Color.red
} label: {
Text("red")
}
Button {
//action code
rowColor = .yellow
} label: {
Text("yellow")
}
Button {
//action code
rowColor = .green
} label: {
Text("green")
}
}
}
}
When I tap edit, it will show a delete button (minus icon). When the delete button tapped it will show the orange delete option (as the gif shows). Now I'm trying to reset the state back to the origin (from orange button back to video name and length ) when the Done button is tapped.
I'm trying few options like closures but nothing much.
Any help would be much appreciated!
My child view Video
struct Video: View {
var videoImage : String
var title : String
var duaration : Int
#Binding var deleteActivated : Bool
var body: some View {
HStack {
Image(videoImage)
...
if deleteActivated {
Button(action: {
}) {
ZStack {
Rectangle()
.foregroundColor(.orange)
.cornerRadius(radius: 10, corners: [.topRight, .bottomRight])
Text("Delete")
.foregroundColor(.white)
}
}
} else {
VStack(alignment: .leading){
....
My parent view VideosDirectory
struct VideosDirectory: View {
#State var videos:[DraftVideos] = [
DraftVideos(isSelected: true,title: "Superman workout", duration: 5, imageURL: "test"),
DraftVideos(isSelected: true,title: "Ironman workout", duration: 15, imageURL: "test1"),
DraftVideos(isSelected: true,title: "Ohman workout and long name", duration: 522, imageURL: "test2")
]
init() {
self._deleteActivated = State(initialValue: Array(repeating: false, count: videos.count))
}
#State private var deleteActivated: [Bool] = []
#State private var show = false
// #State private var editing = false
var body: some View {
// VStack {
NavigationView {
ScrollView(.vertical) {
VStack {
ForEach(videos.indices, id: \.self) { i in
HStack {
if self.show {
Button(action: {
withAnimation {
self.deleteActivated[i].toggle()
}
}) {
Image(systemName: "minus.circle.fill")
...
}
}
Video(videoImage: videos[i].imageURL, title: videos[i].title, duaration: videos[i].duration, deleteActivated: $deleteActivated[i])
}
}
}
.animation(.spring())
}
.navigationBarItems(trailing:
HStack {
Button(action: {
self.show.toggle()
}) {
if self.show {
Text("Done")
} else {
Text("Edit")
}
}
})
}
}
}
Provided code is not testable so just an idea:
Button(action: {
self.deleteActivated = Array(repeating: false, count: videos.count)
self.show.toggle()
}) {
or almost the same but as "post-action" in
}
.animation(.spring())
.onChange(of: self.show) { _ in
// most probably condition is not needed here, but is up to you
self.deleteActivated = Array(repeating: false, count: videos.count)
}
i have a small swiftUI programm in Xcode which let me create and delete Users in a list with a stepper to count points of the users.
everything works fine (adding users, renaming users, stepper counting) unless the deletion of the user.
it throws an error:
Fatal error: Index out of range: file
/Library/Caches/com.apple.xbs/Sources/swiftlang/swiftlang-1103.2.25.8/swift/stdlib/public/core/ContiguousArrayBuffer.swift,
line 444 2020-05-23 12:06:22.854920+0200 Counter[21328:1125981] Fatal
error: Index out of range: file
/Library/Caches/com.apple.xbs/Sources/swiftlang/swiftlang-1103.2.25.8/swift/stdlib/public/core/ContiguousArrayBuffer.swift,
line 444
Here is the code:
import SwiftUI
struct ContentView : View {
#State var isEditing = false
#State var stepperWerte = [3, 5, 7, 9]
#State var editText = ["Player 1", "Player 2", "Player 3", "Player 4"]
var startName = "new Player"
var startLeben = 5
var body: some View {
NavigationView {
List() {
ForEach(0..<editText.count, id: \.self) {
spieler in HStack {
if self.editText.indices.contains(spieler) {
Stepper(value: self.$stepperWerte[spieler], in: -1...10, step: 1, label: {
TextField("", text: self.$editText[spieler], onEditingChanged: {_ in }, onCommit: {self.saveText(id: spieler, Text: self.editText[spieler])} )
.layoutPriority(1)
.fixedSize(horizontal: true, vertical: false)
Text("\(self.stepperWerte[spieler]) - \(spieler) - \(self.editText.count)")})
}
}
}
.onDelete(perform: spielerLoeschen)
.frame(width: nil, height: nil, alignment: .trailing)
}
.navigationBarTitle(Text("Nav_Title"))
.navigationBarItems(leading: Button(action: { self.isEditing.toggle() }) { Text(isEditing ? "Done" : "Edit").frame(width: 85, height: 40, alignment: .leading) },
trailing: Button(action: spielerHinzufuegen, label: { Image(systemName: "plus") }) )
.environment(\.editMode, .constant(self.isEditing ? EditMode.active : EditMode.inactive)).animation(Animation.spring())
}
}
func spielerLoeschen(at offsets: IndexSet) {
stepperWerte.remove(atOffsets: offsets)
editText.remove(atOffsets: offsets)
}
func spielerHinzufuegen() {
stepperWerte.append(startLeben)
editText.append(startName)
}
func saveText(id: Int, Text: String) {
editText[id] = Text
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
(ignore the "if" after the HStack, it has no real effect and those extra prints in the last Text to show the index and the total count)
if i dump the arrays (stepperWerte and editText) they are removed the right way -> the player selected for deletion will be removed correctly from the two arrays.
if i change
TextField("", text: self.$editText[spieler]
to
TextField("", text: self.$editText[0]
it works (unless naturally it displays the first player in all rows and i got the same error after deleting all the players (=rows))
any help would be great - thank you!
According to #Asperi i have changed my code to the following:
import SwiftUI
struct BetterTextField : View {
var container: Binding<[String]>
var index: Int
#State var text: String
var body: some View {
TextField("", text: self.$text, onCommit: {
self.container.wrappedValue[self.index] = self.text
})
.layoutPriority(1)
.fixedSize(horizontal: true, vertical: false)
}
}
struct ContentView : View {
#State var isEditing = false
#State var stepperWerte = [3, 5, 7, 9]
#State var editText = ["Player 1", "Player 2", "Player 3", "Player 4"]
var startName = "new Player"
var startLeben = 5
var body: some View {
NavigationView {
List() {
ForEach(0..<editText.count, id: \.self) {
spieler in HStack {
if self.editText.indices.contains(spieler) {
Stepper(value: self.$stepperWerte[spieler], in: -1...10, step: 1, label: {
BetterTextField(container: self.$editText, index: self.editText.firstIndex(of: self.editText[spieler])!, text: self.editText[spieler])
Text("\(self.stepperWerte[spieler]) - \(spieler) - \(self.editText.count)")})
}
}
}
.onDelete(perform: spielerLoeschen)
.frame(width: nil, height: nil, alignment: .trailing)
}
.navigationBarTitle(Text("Nav_Title"))
.navigationBarItems(leading: Button(action: { self.isEditing.toggle() }) { Text(isEditing ? "Done" : "Edit").frame(width: 85, height: 40, alignment: .leading) },
trailing: Button(action: spielerHinzufuegen, label: { Image(systemName: "plus") }) )
.environment(\.editMode, .constant(self.isEditing ? EditMode.active : EditMode.inactive)).animation(Animation.spring())
}
}
func spielerLoeschen(at offsets: IndexSet) {
stepperWerte.remove(atOffsets: offsets)
editText.remove(atOffsets: offsets)
}
func spielerHinzufuegen() {
stepperWerte.append(startLeben)
editText.append(startName)
}
func saveText(id: Int, Text: String) {
editText[id] = Text
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
... and it works - thank you!
but:
is this a bug in SwiftUI or intentional?
The problem is that you are not using your items directly in the ForEach loop. Consider using structs for your data as objects and make them identifiable.
struct Player : Identifiable {
let id = UUID()
var stepperWerte: Int
var editText : String
}
struct ContentView : View {
#State var isEditing = false
#State var players = [Player(stepperWerte: 3, editText: "Player 1"), Player(stepperWerte: 5, editText: "Player 2"), Player(stepperWerte: 7, editText: "Player 3"), Player(stepperWerte: 9, editText: "Player 4")]
var startName = "new Player"
var startLeben = 5
var body: some View {
NavigationView {
List() {
ForEach(self.players) { player in
SecondView(player: player)
}
.onDelete(perform: spielerLoeschen)
}
.frame(width: nil, height: nil, alignment: .trailing)
.navigationBarTitle(Text("Nav_Title"))
.navigationBarItems(leading: Button(action: { self.isEditing.toggle() }) { Text(isEditing ? "Done" : "Edit").frame(width: 85, height: 40, alignment: .leading) },
trailing: Button(action: spielerHinzufuegen, label: { Image(systemName: "plus") }) )
.environment(\.editMode, .constant(self.isEditing ? EditMode.active : EditMode.inactive)).animation(Animation.spring())
}
}
func spielerLoeschen(at offsets: IndexSet) {
players.remove(atOffsets: offsets)
}
func spielerHinzufuegen() {
players.insert(Player(stepperWerte: 4, editText: "Neuer Player"), at: 0)
}
}
struct SecondView : View {
var player : Player
#State var stepperWerte : Int
#State var name : String
init(player : Player)
{
self._stepperWerte = State(initialValue: player.stepperWerte)
self._name = State(initialValue: player.editText)
self.player = player
}
var body: some View
{
Stepper(value: self.$stepperWerte, in: -1...10, step: 1, label: {
TextField("", text: self.$name)
.layoutPriority(1)
.fixedSize(horizontal: true, vertical: false)
Text("\(player.stepperWerte)")
})
}
}
I created a struct Player, and then an array of many Players. In the ForEach you can directly use players as Player confirms to Identifiable protocol. This is way easier as you can access a player object in your ForEach loop and you do not have to access everything with indices. In the deleting function you just delete the object out of the array or add something new to it. Deleting now works fine.
I have removed some code from the list row, just to reproduce it easier, just if you are wondering.
In a small sample SwiftUI app, I have a settings view that shows a couple of option selections, implemented as segmented controls. The text in these segmented controls visibly moves when an alert is presented or dismissed. Is there a way to get rid of this glitch?
Paste this in a Playground to reproduce:
import SwiftUI
import PlaygroundSupport
struct FlickeringSegmentsView: View {
#State var option = 0
#State var alerting = false
var body: some View {
VStack(alignment: .center, spacing: 120) {
Picker("options", selection: $option) {
Text("Option A").tag(0)
Text("Option B").tag(1)
}
.pickerStyle(SegmentedPickerStyle())
.padding(16)
Button(action: { self.alerting.toggle() },
label: { Text("Show Alert") }
)
.alert(isPresented: $alerting) {
Alert(title: Text("Alert"))
}
}
}
}
PlaygroundPage.current.setLiveView(FlickeringSegmentsView())
This issue is resolved in Xcode 12 beta using the included iOS 14 simulator (and hopefully stays that way).
I hope code below should help you:
public protocol SegmentedPickerViewElementTraits: Hashable {
var localizedText: String { get }
}
public struct SegmentedPickerView<Value, Data, ID, Label>: View
where
Value: SegmentedPickerViewElementTraits,
Data: RandomAccessCollection,
Data.Element == Value,
ID: Hashable,
Label: View {
public let data: Data
public let id: KeyPath<Data.Element, ID>
public let selection: Binding<Value>
public let label: Label
public init(data: Data,
id: KeyPath<Data.Element, ID>,
selection: Binding<Value>,
label: Label) {
self.data = data
self.id = id
self.selection = selection
self.label = label
}
public var body: some View {
Picker(selection: selection, label: label) {
ForEach(data, id: id) {
Text($0.localizedText).tag($0)
}
}
.pickerStyle(SegmentedPickerStyle())
}
}
and lets modify your code:
enum Options: UInt8, CaseIterable {
case optionA
case optionB
}
extension Options: SegmentedPickerViewElementTraits {
var localizedText: String {
switch self {
case .optionA:
return "Option A"
case .optionB:
return "Option B"
}
}
}
struct FlickeringSegmentsView: View {
#State
var option: Options = .optionA
#State
var alerting = false
var body: some View {
VStack(alignment: .center, spacing: 120) {
SegmentedPickerView(
data: Options.allCases,
id: \.self,
selection: $option,
label: Text("options")
)
.padding(16)
Button(
action: { self.alerting.toggle() },
label: { Text("Show Alert") }
)
.alert(isPresented: $alerting) {
Alert(title: Text("Alert"))
}
}
}
}