I have a custom TabView and I want to Bind to a State to change tabs. I also want to detect if the user has tapped the same tab again in order to scroll to the top of that view.
didSet isn't called when I use a binding. onChange isn't called because the value hasn't changed, and onReceive doesn't give me the old value to compare.
Any ideas? (Trying to avoid using a published property)
struct ContentView: View {
#State private var scrollToTop1: Bool = false
#State private var scrollToTop2: Bool = false
#State private var selectedTab: Int = 1
var body: some View {
ZStack(alignment: .bottom) {
TabView(selection: $selectedTab) {
NavigationView {
View1(scrollToTop: $scrollToTop1)
}
.tag(1)
NavigationView {
View2(scrollToTop: $scrollToTop2)
}
.tag(2)
}
.onReceive(Just(selectedTab)) { [oldValue = selectedTab] newValue in
print("Old: \(oldValue)") //Shows newValue
print("New: \(newValue)")
if oldValue == newValue {
switch selectedTab {
case 1:
scrollToTop1.toggle()
case 2:
scrollToTop2.toggle()
default:
break
}
}
}
TabBar(selectedTab: $selectedTab)
}
}
}
struct TabBar: View {
#Binding var selectedTab: Int
var body: some View {
HStack {
TabItem(selectedTab: $selectedTab, text: "View 1", tab: 1)
TabItem(selectedTab: $selectedTab, text: "View 2", tab: 2)
}
.background(Color.green)
}
}
struct TabItem: View {
#Binding var selectedTab: Int
let text: String
let tab: Int
var body: some View {
Button {
selectedTab = tab
} label: {
Text(text)
}
.frame(maxWidth: .infinity)
.frame(height: 50)
}
}
I think this is a great scenario for a custom Binding, where you can intercept the value before its set and compare it:
struct ContentView: View {
#State private var scrollToTop1: Bool = false
#State private var scrollToTop2: Bool = false
#State private var selectedTab: Int = 1
var customBinding: Binding<Int> {
.init {
selectedTab
} set: { newValue in
print("New value: ", newValue)
if newValue == selectedTab {
print("Scroll to top")
}
selectedTab = newValue
}
}
var body: some View {
ZStack(alignment: .bottom) {
TabView(selection: customBinding) {
NavigationView {
Text("1")
}
.tag(1)
NavigationView {
Text("2")
}
.tag(2)
}
TabBar(selectedTab: customBinding)
}
}
}
struct TabBar: View {
#Binding var selectedTab: Int
var body: some View {
HStack {
TabItem(selectedTab: $selectedTab, text: "View 1", tab: 1)
TabItem(selectedTab: $selectedTab, text: "View 2", tab: 2)
}
.background(Color.green)
}
}
I am trying to get data from one view to another.
I can not figure out how to get values from the fourth view array into the Third view.
I am not using storyboards. I tried using #EnvironmentObject but can not make it work. New to coding. In xcode I am using watchos without app.
I tried to strip out most of the code and leave just the important stuff that can be tested. I used NavigationLink(destination: )to transfer between views.
enter code here
class viewFromEveryWhere: ObservableObject {
#Published var testname2: String = "testTTname"
}
struct secondView: View {
var body: some View {
Text("second view")
List(1..<7) {
Text("\($0)")
}
}
}
struct thirdView: View {
#EnvironmentObject var testname2: viewFromEveryWhere
#EnvironmentObject var testSixTestArray: viewFromEveryWhere
#State var sixTestArray:[String] = ["ww","GS","DW","CD","TS","JW",]
var body: some View {
List(sixTestArray, id:\.self) {
Text($0)
}
}
}
struct fourthView: View {
#StateObject var testname2 = viewFromEveryWhere()
#State private var name: String = ""
#State var testSixTestArray:[String] = []
func collectName () {
print("collectName triggered")
if testSixTestArray.count < 5 {
// testSixTestArray.append(name)
print(name)
print(testSixTestArray)
}
// .enviromentObject(testSixTestArray)
}
var body: some View {
VStack(alignment: . leading) {
Text("Type a name")
TextField("Enter your name", text: $name)
Text("Click to add, \(name)!")
// Button("click this if \(name) is correct") {}
Button(action:{
print("Button Tapped")
collectName()
print(testSixTestArray.count)
name = ""
}) {
Text("Add \(name) to list")
}
// .buttonStyle(GrowingButton1())
}
Text("forth view")
// testSixTestArray.append(name)
.environmentObject(testname2)
}
}
/*func presentTextInputControllerWithSuggestions(forLanguage suggestionsHandler:
((String)-> [Any]?)?,
allowedInputMode inputMode:
WKTextInputMode,
completion: #escaping ([Any]?) -> Void) {}
*/
struct ContentView: View {
#State var sixNameArray:[String] = ["","","","","","",]
#State var messageTextBox: String = "Start"
#State var button1: String = "Button 1"
#State var button2: String = "Button 2"
#State var button3: String = "Button 3"
var body: some View {
NavigationView {
VStack{
Text(messageTextBox)
.frame(width: 120, height: 15, alignment: .center)
.truncationMode(.tail)
.padding()
NavigationLink(destination: secondView(),
label:{
Text(button1)
})
.navigationBarTitle("Main Page")
NavigationLink(destination: thirdView(),
label:{
Text(button2)
})
NavigationLink(destination: fourthView(),
label:{
Text(button3)
})
}
}
}
}
enter code here
Im trying to make an app similar to iphones reminders app in terms of UI. I have a view where I have a list that I can add and delete items from, I also have a view for adding an item that allows me to name the item and select some options, and I have another view for when an item in the list is selected and I want to be able to show the name and options I made but it doesn't display. The code for the list view
struct DotView: View {
enum ActiveSheet: String, Identifiable {
case SwiftUIView, EditView
var id: String {
return self.rawValue
}
}
#EnvironmentObject var listViewModel: ListViewModel
#AppStorage("dotApiKey") var selectedDotApi: String = ""
#State var dotName:String = ""
#State var dotNumber:String = ""
#State var selection:String = ""
#State var triggerSelection:String = ""
#State var searchText:String = ""
#State var plugUsername:String = ""
#State var plugPassword:String = ""
#State var toggleNotification:Bool
#State var activeSheet : ActiveSheet? = nil
#Binding var show : Bool
var body: some View {
ZStack{
HStack {
EditButton()
.padding(.leading)
Spacer()
Button(action: {
self.activeSheet = .SwiftUIView
}, label: {
Text("Add")
})
.padding(.trailing)
}
ZStack {
List {
ForEach(listViewModel.dotitems, id:\.dotId){ dotitem in
Button(action: { self.activeSheet = .EditView }, label: {
DotItemListView(dotitem: dotitem)
})
}
.onDelete(perform: listViewModel.deleteItem)
.onMove(perform: listViewModel.moveItem)
.listRowBackground(Color("textBG"))
}.listStyle(PlainListStyle())
.background(Color("textBG"))
.frame(height: 300)
.cornerRadius(10)
.padding(.horizontal)
}
}
.sheet(item: $activeSheet){ sheet in
switch sheet {
case .SwiftUIView:
SwiftUIView(dotName: dotName, dotNumber: dotNumber, selection: selection, triggerSelection: triggerSelection, searchText: searchText, plugUsername: plugUsername, plugPassword: plugPassword, show: $show)
case .EditView:
EditView(show:$show)
}
}
When I add an item to the list it shows this in each row
struct DotItemListView:View {
let dotitem: DotItem
var body: some View{
HStack {
Text(dotitem.dotName)
Spacer()
Text(dotitem.selection)
Spacer()
Text(dotitem.dotNumber)
Spacer()
}
}
}
This is how I'm adding each item to the list
struct DotItem:Equatable, Codable{
var dotId = UUID().uuidString
let dotName:String
let dotNumber:String
let selection:String
}
class ListViewModel: ObservableObject {
#Published var dotitems: [DotItem] = [] {
didSet {
saveItem()
}
}
let dotitemsKey: String = "dotitems_list"
init() {
getDotItems()
}
func getDotItems() {
guard
let data = UserDefaults.standard.data(forKey: dotitemsKey),
let savedDotItems = try? JSONDecoder().decode([DotItem].self, from: data)
else { return }
self.dotitems = savedDotItems
}
func deleteItem(indexSet: IndexSet){
dotitems.remove(atOffsets: indexSet)
}
func moveItem(from: IndexSet, to: Int){
dotitems.move(fromOffsets: from, toOffset: to)
}
func addItem(dotName: String, dotNumber: String, selection: String){
let newItem = DotItem(dotName: dotName, dotNumber: dotNumber, selection: selection)
dotitems.append(newItem)
print(newItem)
}
func saveItem() {
if let encodedData = try? JSONEncoder().encode(dotitems) {
UserDefaults.standard.set(encodedData, forKey: dotitemsKey)
}
}
}
This is the view that I'm entering the data for each item
struct SwiftUIView: View {
#Environment(\.presentationMode) var presentationMode
#EnvironmentObject var listViewModel: ListViewModel
#AppStorage("dotApiKey") var selectedDotApi: String = ""
#State var dotName:String
#State var dotNumber:String
#State var selection:String
#State var triggerSelection:String
#State var selectedColor = Color.black
#State var searchText:String
#State var plugUsername:String
#State var plugPassword:String
#ObservedObject var vm = getDeviceNames()
#State var triggerDot:Bool = false
#State var toggleOnOff:Bool = false
#State var toggleLightColor:Bool = false
#State var isSearching:Bool = false
#StateObject var camera = CameraModel()
#Binding var show : Bool
var body: some View {
NavigationView {
Form {
Section(header: Text("Info")) {
TextField("Name", text: $dotName)
TextField("Number", text: $dotNumber)
Picker(selection: $selection, label: Text("Discover Plug")) {
ForEach(vm.dataSet, id:\.self) { item in
Text(item.Device).tag(item.Device)
}
}
Toggle(isOn: $isSearching, label: {
Text("Have smart plugs?")
})
if isSearching {
HStack{
Text("Casa")
Spacer()
Button(action: {sendPlugDict()}, label: {
Text("Login")
})
}
TextField("Username", text: $plugUsername)
TextField("Password", text: $plugPassword)
}
}
Section {
Toggle(isOn: $toggleOnOff, label: {
Text("On/Off")
})
}
Section {
Toggle(isOn: $toggleLightColor, label: {
Text("Light Color")
})
if toggleLightColor {
ColorPicker("Choose Light Color", selection: $selectedColor)
}
}
Section {
if listViewModel.dotitems.isEmpty == false {
Toggle(isOn: $triggerDot, label: {
Text("Add a DOT to trigger")
})
if triggerDot {
Picker(selection: $triggerSelection, label: Text("Select DOT")) {
ForEach(listViewModel.dotitems, id:\.dotId){ dotitem in
DotItemListView(dotitem: dotitem)
}
}
}
}
}
}
.navigationBarTitle("")
.navigationBarHidden(true)
}
}
}
and this is the view that I'm trying to show the data when any list item is selected which is basically the same as above except in a few places
struct EditView: View {
#Environment(\.presentationMode) var presentationMode
#EnvironmentObject var listViewModel: ListViewModel
#ObservedObject var vm = getDeviceNames()
#State var dataSet = [Result]()
#State var dotName:String = ""
#State var dotNumber:String = ""
#State var selection:String = ""
#State var triggerSelection:String = ""
#State var selectedColor = Color.black
#State var searchText:String = ""
#State var plugUsername:String = ""
#State var plugPassword:String = ""
#State var triggerDot:Bool = false
#State var toggleOnOff:Bool = false
#State var toggleLightColor:Bool = false
#State var isSearching:Bool = false
#Binding var show : Bool
var body: some View {
NavigationView {
Form {
Section(header: Text("Info")) {
TextField(dotName, text: $dotName)
TextField(dotNumber, text: $dotNumber)
Picker(selection: $selection, label: Text("Discorver Plug")) {
ForEach(dataSet, id:\.self) { item in
Text(item.Device).tag(item.Device)
}
}
Toggle(isOn: $isSearching, label: {
Text("Have smart plugs?")
})
if isSearching {
HStack{
Text("Casa")
Spacer()
Button(action: {
SwiftUIView(dotName: dotName, dotNumber: dotNumber, selection: selection, triggerSelection: triggerSelection, searchText: searchText, plugUsername: plugUsername, plugPassword: plugPassword, show: $show).sendPlugDict()}, label: {
Text("Login")
})
}
TextField("Username", text: $plugUsername)
TextField("Password", text: $plugPassword)
}
}
Section {
Toggle(isOn: $toggleOnOff, label: {
Text("On/Off")
})
}
Section {
Toggle(isOn: $toggleLightColor, label: {
Text("Light Color")
})
if toggleLightColor {
ColorPicker("Choose Light Color", selection: $selectedColor)
}
}
Section {
if listViewModel.dotitems.isEmpty == false {
Toggle(isOn: $triggerDot, label: {
Text("Add a DOT to trigger")
})
if triggerDot {
Picker(selection: $triggerSelection, label: Text("Select DOT")) {
ForEach(listViewModel.dotitems, id:\.dotId){ dotitem in
DotItemListView(dotitem: dotitem)
}
}
}
}
}
}
.navigationBarTitle("")
.navigationBarHidden(true)
}
}
}
I know this is a lot to go through but any help would be greatly appreciated
use #Binding on the view that you want to pass state to .
View1 :
#State var a = true
View2(a: $a)
View2 :
#Binding var a : Bool
for passing data globally use #EnvoirmentObject :
example :
#main
struct YourApp: App {
#StateObject var session = Session()
var body: some Scene {
WindowGroup {
SplashView()
.environmentObject(session)
}
}
}
Session :
class Session: ObservableObject {
/// user signin state
#Published var isSignedIn : Bool = false
}
View1 ,View2 , View3 .... :
struct SplashView: View {
#EnvironmentObject var session : Session
var body: some View {
VStack{
SignInView()
.opacity(session.isSignedIn ? 0:1)
}
.background(Color.background.ignoresSafeArea())
}
}
I create a List to show my player information. but my List column didn't fit my cell view. The textfield that disappear in some column. This is strange because most of they has "-1" number. Is somebody know any reason may cause it happen? Demo video: https://youtu.be/vUo9zZZ5olo
struct SelectPlayerCellView: View {
#ObservedObject var player: PlayerGameData
#State var newuniformNumber: Int = 0
var body: some View {
HStack{
Button(action: {
self.player.isPlayer.toggle()
}){
if self.player.isPlayer{
Image(systemName: "checkmark.rectangle")
}
else{
Image(systemName: "rectangle")
}
}
TextField("\(player.uniformNumber)", value: $newuniformNumber, formatter: NumberFormatter())
.font(.system(size: 40))
.frame(width:55)
.padding(5)
Text(player.name)
}
.padding(.horizontal,8)
.cornerRadius(20)
}
}
I put my cell view in the list
struct SelectPlayerView: View {
#EnvironmentObject var teamResult : TeamResult
#ObservedObject var allPlayerList : PlayersGameData = PlayersGameData()
#State var goToNextPage : Bool = false
var selectedPlayerList : PlayersGameData = PlayersGameData()
var body: some View {
VStack {
List{
ForEach(allPlayerList.playersGameDataArray, id: \.userId) { (player) in
SelectPlayerCellView(player: player)
}.onMove(perform: move)
}
NavigationLink(destination: SelectStartingLineupView(selectedPlayerList: self.selectedPlayerList).environmentObject(teamResult),isActive: $goToNextPage){
EmptyView()
}
VStack (spacing: 10){
Button(action: {
self.createPlayersList()
self.goToNextPage.toggle()
}){
Image(systemName: "arrowshape.turn.up.right.circle")
}
}
}.onAppear(perform: getTeamMemberResults)
.onDisappear(perform: clearTeamMemberResults)
.navigationBarItems(trailing: EditButton())
}
I don't want my Login Page and main page show navigationBar, but i need Navigation in RegistrationPage and forgetPasswordPage. I use .navigationBarHidden(self.isNavigationBarHidden) to control my navigationBar.
The mainPage with a tabView and there are some tabItemView with their own NavigationView.
It seemed that my app with a block on the top. When I jump to a sheet and go back to main page, the blank was disappear.
Here is the video for my issue.
https://youtu.be/rIZ3Awzpe3Y
struct LoginView: View {
#EnvironmentObject var userToken: UserToken
#State var account : String = ""
#State var password : String = ""
#State var hint : String = ""
#State var successLogin : Bool = false
#State var hideLoginNavigationBar : Bool = false
#State private var isNavigationBarHidden = true
var body: some View {
NavigationView {
NavigationLink(destination: ResetPassword(isNavigationBarHidden: self.$isNavigationBarHidden)) {
LoginButtonTextStyle(TextValue:"Resetpassword")
}
Button(action: LoginCheck) {
// log in logic
NavigationLink(destination: MainView(isNavigationBarHidden: self.$isNavigationBarHidden), isActive: $successLogin) {
EmptyView()
}
LoginButtonTextStyle(TextValue:"Login")
}
NavigationLink(destination: RegistrationView(isNavigationBarHidden: self.$isNavigationBarHidden)) {
LoginButtonTextStyle(TextValue:"Registration")
}
}.padding(EdgeInsets(top: 0, leading: 50, bottom: 0, trailing: 50))
}
.navigationBarTitle("Login",displayMode: .inline)
.navigationBarHidden(self.isNavigationBarHidden)
.onAppear {
self.isNavigationBarHidden = true
}
}
}
Here is my main page
struct MainView: View {
#State var selectedtab:Int = 1
#Binding var isNavigationBarHidden : Bool
var body: some View {
TabView (selection: $selectedtab){
UserInformationView()
.tabItem {
Image(systemName: "person.fill")
}.tag(0)
.navigationBarTitle("Main")
.navigationBarHidden(true)
TeamListView()
.tabItem {
Image(systemName: "person.3.fill")
}.tag(1)
.navigationBarTitle("Main")
.navigationBarHidden(true)
NotificationView()
.tabItem {
Image(systemName: "bell.fill")
}.tag(2)
.navigationBarTitle("Main")
.navigationBarHidden(true)
SettingView()
.tabItem {
Image(systemName: "list.bullet")
}.tag(3)
.navigationBarTitle("Main")
.navigationBarHidden(true)
}
.navigationBarTitle("Main")
.onAppear(perform: {
self.isNavigationBarHidden = true
})
}
}
one of my tab item view with NavigationView
struct TeamListView: View {
var tutors: [Tutor] = teamData
// var teams: [Team]?
#Environment(\.presentationMode) var presentationMode
#EnvironmentObject var userToken : UserToken
#State var showingDelete = false
#State var teamListFloatingButtonShow = false
#State var goToCreateTeam = false
#State var goToInputInvitionCode = false
#State var addMenuIsPresented: Bool = false
#State var creatTeamIsPresented: Bool = false
#State var teamResults : [TeamResult] = []
var body: some View {
NavigationView {
.....
}
.onAppear(perform: getTeamData)
}