'loginpage.Type' is not convertible to '(Binding<Bool>) -> loginpage' - boolean

I am trying to redirect the user to a login page, where they can login using firebase but am given the error above.
I am also using the same code (but altered) to redirect them after they have entered the correct username and password.
First Page
import SwiftUI
struct onboarding1: View {
#Binding var isOnboardingDone: Bool
init(_ isOnboardingDone: Binding<Bool>) {
self._isOnboardingDone = isOnboardingDone
}
var body: some View {
NavigationView {
ZStack {
Color("background").edgesIgnoringSafeArea(.all)
VStack {
Image("logo")
Button(action: {
self.isOnboardingDone.toggle()
}) {
Text("toggle")
}
}
}
}
}
}
struct shmuck_Previews: PreviewProvider {
static var previews: some View {
onboarding1(.constant(false))
}
}
Other Page
import SwiftUI
struct supporter: View {
#State var isOnboardingDone = false
var body: some View {
Group {
if isOnboardingDone {
loginpage() // error displayed here ('loginpage.Type' is not convertible to '(Binding<Bool>) -> loginpage')
} else {
onboarding1($isOnboardingDone)
}
}
}
}
struct supporter_Previews: PreviewProvider {
static var previews: some View {
supporter()
}
}
As requested here is the login page code
Login Page
import SwiftUI
import Firebase
struct loginpage: View {
#Binding var sos: Bool
init(_ sos: Binding<Bool>) {
self._sos = sos
}
#State var email = ""
#State var password = ""
var body: some View {
NavigationView {
ZStack {
Color("background").edgesIgnoringSafeArea(.all)
VStack {
Image("fulllogo")
.offset(y: -100)
Text("username")
.offset(y: -200)
TextField("Enter your email", text: $email)
.offset(y: -200)
Text("Password")
.offset(y: -200)
TextField("Enter your password", text: $password)
.offset(y: -200)
Button(action: {
Auth.auth().signIn(withEmail: self.email, password: self.password) { (result, error) in
if error != nil {
print(error?.localizedDescription)
} else {
// user signed in
self.sos.toggle()
}
}
}) {
RoundedRectangle(cornerRadius: 10)
.frame(width: 300, height: 40)
.overlay(
Text("Let's Go!")
.foregroundColor(.white)
)
}.offset(y: -180)
}.navigationBarTitle("Login")
}
}
}
}
struct heyMama_Previews: PreviewProvider {
static var previews: some View {
loginpage(.constant(false))
}
}
Other Page
import SwiftUI
struct login2: View {
#State var sos = false
var body: some View {
Group {
if sos {
LoggedIn()
} else {
loginpage($sos)
}
}
}
}
struct login2_Previews: PreviewProvider {
static var previews: some View {
login2()
}
}

As you've declared #Binding var sos: Bool & init it using init(_ sos: Binding<Bool>) method in loginpage, You must have to pass binding variable as argument.
You need to pass binding variable like #State. You can refer this answer for more details about #Binding
Try the below code.
struct supporter: View {
#State var isOnboardingDone = false
#State var sos = false
var body: some View {
Group {
if isOnboardingDone {
loginpage(sos: $sos)
} else {
onboarding1($isOnboardingDone)
}
}
}
}
So, whenever you value of sos variable will change from loginpage it will also reflect on supporter page.

The Code Below Works.
struct supporter: View {
#State var isOnboardingDone = false
#State var sos = false
var body: some View {
Group {
if isOnboardingDone {
loginpage($sos)
} else {
onboarding1($isOnboardingDone)
}
}
}
}

Related

ConfirmationDialog cancel bug in swiftui

When I jump to the settings page and click Cancel in the pop-up dialog box, it will automatically return to the home page. How can I avoid this problem?
import SwiftUI
struct ContentView: View {
#State private var settingActive: Bool = false
var body: some View {
NavigationView {
TabView {
VStack {
NavigationLink(destination: SettingView(), isActive: $settingActive) {
EmptyView()
}
Button {
settingActive.toggle()
} label: {
Text("Setting")
}
}
.padding()
}
}
}
}
struct SettingView: View {
#State private var logoutActive: Bool = false
var body: some View {
VStack {
Button {
logoutActive.toggle()
} label: {
Text("Logout")
}
.confirmationDialog("Logout", isPresented: $logoutActive) {
Button("Logout", role: .destructive) {
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
This seems to be an issue with using TabView inside NavigationView.
You can solve this by moving TabView to be your top level object (where it really should be), or replacing NavigationView with the new NavigationStack.
Here's an implementation that also removes the deprecated NavigationLink method:
enum Router {
case settings
}
struct ContentView: View {
#State private var path = NavigationPath()
var body: some View {
TabView {
NavigationStack(path: $path) {
VStack {
Button {
path.append(Router.settings)
} label: {
Text("Setting")
}
}
.navigationDestination(for: Router.self) { router in
switch router {
case .settings:
SettingView(path: $path)
}
}
.padding()
}
}
}
}
struct SettingView: View {
#Binding var path: NavigationPath
#State private var logoutActive: Bool = false
var body: some View {
VStack {
Button {
logoutActive = true
} label: {
Text("Logout")
}
.confirmationDialog("Logout", isPresented: $logoutActive) {
Button("Logout", role: .destructive) {
}
}
}
}
}

SwiftUI - Save status when language is selected

I have successfully displayed the language in the UI, but I have a problem: when I click the "Save" button it still doesn't save the language I selected.
I want when I have selected the language and clicked the Save Button , it will return to the previous page and when I click it again it will show the language I selected before.
Above data is simulation only, I mainly focus on its features
All my current code
struct ContentView: View {
var language: String? = ""
var body: some View {
NavigationView {
HStack {
NavigationLink(destination:LanguageView(language: language)) {
Text("Language")
.padding()
Spacer()
Text(language!)
.padding()
}
}
}
}
}
struct LanguageView: View {
#Environment(\.presentationMode) var pres
#State var language: String?
#State var selectedLanguage: String? = ""
var body: some View {
VStack {
CustomLanguageView(selectedLanguage: $language)
Button(action: {
language = selectedLanguage
pres.wrappedValue.dismiss()
})
{
Text("Save")
.foregroundColor(.black)
}
.padding()
Spacer()
}
}
}
struct CustomLanguageView: View {
var language = ["US", "English", "Mexico", "Canada"]
#Binding var selectedLanguage: String?
var body: some View {
LazyVStack {
ForEach(language, id: \.self) { item in
SelectionCell(language: item, selectedLanguage: self.$selectedLanguage)
.padding(.trailing,40)
Rectangle().fill(Color.gray)
.frame( height: 1,alignment: .bottom)
}
.frame(height:15)
}
}
}
struct SelectionCell: View {
let language: String
#Binding var selectedLanguage: String?
var body: some View {
HStack {
Text(language)
Spacer()
if language == selectedLanguage {
Image(systemName: "checkmark")
.resizable()
.frame(width:20, height: 15)
}
}
.onTapGesture {
self.selectedLanguage = self.language
}
}
}
Edit to my previous answer, since something is blocking my edit to my previous answer, this one shows all the code I used to make it works well for me:
import SwiftUI
#main
struct TestApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct ContentView: View {
#State var language: String? = ""
var body: some View {
NavigationView {
HStack {
NavigationLink(destination: LanguageView(language: $language)) {
Text("Language").padding()
Spacer()
Text(language!).padding()
}
}
}
}
}
struct LanguageView: View {
#Environment(\.presentationMode) var pres
#Binding var language: String?
var body: some View {
VStack {
CustomLanguageView(selectedLanguage: $language) // <--- here
Button(action: { pres.wrappedValue.dismiss() }) { // <--- here
Text("Save").foregroundColor(.black)
}.padding()
Spacer()
}
}
}
struct CustomLanguageView: View {
var language = ["US", "English", "Mexico", "Canada"]
#Binding var selectedLanguage: String?
var body: some View {
LazyVStack {
ForEach(language, id: \.self) { item in
SelectionCell(language: item, selectedLanguage: self.$selectedLanguage).padding(.trailing,40)
Rectangle().fill(Color.gray).frame( height: 1,alignment: .bottom)
}.frame(height:15)
}
}
}
struct SelectionCell: View {
let language: String
#Binding var selectedLanguage: String?
var body: some View {
HStack {
Text(language)
Spacer()
if language == selectedLanguage {
Image(systemName: "checkmark").resizable().frame(width:20, height: 15)
}
}
.onTapGesture {
self.selectedLanguage = self.language
}
}
}
you could use #State var language throughout and use dismiss, such as this,
to achieve what you want:
struct LanguageView: View {
#Environment(\.dismiss) var dismiss // <--- here
#Binding var language: String?
var body: some View {
VStack {
CustomLanguageView(selectedLanguage: $language) // <--- here
Button(action: {
dismiss() // <--- here
})
{
Text("Save").foregroundColor(.black)
}
.padding()
Spacer()
}
}
}

How to setup NavigationLink in SwiftUI sheet to redirect to new view

I am attempting to build a multifaceted openweathermap app. My app is designed to prompt the user to input a city name on a WelcomeView, in order to get weather data for that city. After clicking search, the user is redirected to a sheet with destination: DetailView, which displays weather details about that requested city. My goal is to disable dismissal of the sheet in WelcomeView and instead add a navigationlink to the sheet that redirects to the ContentView. The ContentView in turn is set up to display a list of the user's recent searches (also in the form of navigation links).
My issues are the following:
The navigationLink in the WelcomeView sheet does not work. It appears to be disabled. How can I configure the navigationLink to segue to destination: ContentView() ?
After clicking the navigationLink and redirecting to ContentView, I want to ensure that the city name entered in the WelcomeView textfield is rendered as a list item in the ContentView. For that to work, would it be necessary to set up an action in NavigationLink to call viewModel.fetchWeather(for: cityName)?
Here is my code:
WelcomeView
struct WelcomeView: View {
#StateObject var viewModel = WeatherViewModel()
#State private var cityName = ""
#State private var showingDetail: Bool = false
#State private var linkActive: Bool = true
#State private var acceptedTerms = false
var body: some View {
Section {
HStack {
TextField("Search Weather by City", text: $cityName)
.padding()
.overlay(RoundedRectangle(cornerRadius: 10.0).strokeBorder(Color.gray, style: StrokeStyle(lineWidth: 1.0)))
.padding()
Spacer()
Button(action: {
viewModel.fetchWeather(for: cityName)
cityName = ""
self.showingDetail.toggle()
}) {
HStack {
Image(systemName: "plus")
.font(.title)
}
.padding(15)
.foregroundColor(.white)
.background(Color.green)
.cornerRadius(40)
}
.sheet(isPresented: $showingDetail) {
VStack {
NavigationLink(destination: ContentView()){
Text("Return to Search")
}
ForEach(0..<viewModel.cityNameList.count, id: \.self) { city in
if (city == viewModel.cityNameList.count-1) {
DetailView(detail: viewModel.cityNameList[city])
}
}.interactiveDismissDisabled(!acceptedTerms)
}
}
}.padding()
}
}
}
struct WelcomeView_Previews: PreviewProvider {
static var previews: some View {
WelcomeView()
}
}
ContentView
let coloredToolbarAppearance = UIToolbarAppearance()
struct ContentView: View {
// Whenever something in the viewmodel changes, the content view will know to update the UI related elements
#StateObject var viewModel = WeatherViewModel()
#State private var cityName = ""
#State var showingDetail = false
init() {
// toolbar attributes
coloredToolbarAppearance.configureWithOpaqueBackground()
coloredToolbarAppearance.backgroundColor = .systemGray5
UIToolbar.appearance().standardAppearance = coloredToolbarAppearance
UIToolbar.appearance().scrollEdgeAppearance = coloredToolbarAppearance
}
var body: some View {
NavigationView {
VStack() {
List () {
ForEach(viewModel.cityNameList) { city in
NavigationLink(destination: DetailView(detail: city)) {
HStack {
Text(city.name).font(.system(size: 32))
Spacer()
Text("\(city.main.temp, specifier: "%.0f")°").font(.system(size: 32))
}
}
}.onDelete { index in
self.viewModel.cityNameList.remove(atOffsets: index)
}
}.onAppear() {
viewModel.fetchWeather(for: cityName)
}
}.navigationTitle("Weather")
.toolbar {
ToolbarItem(placement: .bottomBar) {
HStack {
TextField("Enter City Name", text: $cityName)
.frame(minWidth: 100, idealWidth: 150, maxWidth: 240, minHeight: 30, idealHeight: 40, maxHeight: 50, alignment: .leading)
Spacer()
Button(action: {
viewModel.fetchWeather(for: cityName)
cityName = ""
self.showingDetail.toggle()
}) {
HStack {
Image(systemName: "plus")
.font(.title)
}
.padding(15)
.foregroundColor(.white)
.background(Color.green)
.cornerRadius(40)
}.sheet(isPresented: $showingDetail) {
ForEach(0..<viewModel.cityNameList.count, id: \.self) { city in
if (city == viewModel.cityNameList.count-1) {
DetailView(detail: viewModel.cityNameList[city])
}
}
}
}
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
DetailView
struct DetailView: View {
var detail: WeatherModel
var body: some View {
VStack(spacing: 20) {
Text(detail.name)
.font(.system(size: 32))
Text("\(detail.main.temp, specifier: "%.0f")°")
.font(.system(size: 44))
Text(detail.firstWeatherInfo())
.font(.system(size: 24))
}
}
}
struct DetailView_Previews: PreviewProvider {
static var previews: some View {
DetailView(detail: WeatherModel.init())
}
}
ViewModel
class WeatherViewModel: ObservableObject {
#Published var cityNameList = [WeatherModel]()
func fetchWeather(for cityName: String) {
guard let url = URL(string: "https://api.openweathermap.org/data/2.5/weather?q=\(cityName)&units=imperial&appid=<MyAPIKey>") else { return }
let task = URLSession.shared.dataTask(with: url) { data, _, error in
guard let data = data, error == nil else { return }
do {
let model = try JSONDecoder().decode(WeatherModel.self, from: data)
DispatchQueue.main.async {
self.cityNameList.append(model)
}
}
catch {
print(error) // <-- you HAVE TO deal with errors here
}
}
task.resume()
}
}
Model
struct WeatherModel: Identifiable, Codable {
let id = UUID()
var name: String = ""
var main: CurrentWeather = CurrentWeather()
var weather: [WeatherInfo] = []
func firstWeatherInfo() -> String {
return weather.count > 0 ? weather[0].description : ""
}
}
struct CurrentWeather: Codable {
var temp: Double = 0.0
}
struct WeatherInfo: Codable {
var description: String = ""
}
DemoApp
#main
struct SwftUIMVVMWeatherDemoApp: App {
var body: some Scene {
WindowGroup {
// ContentView()
WelcomeView()
}
}
}

How to create a NavigationManager that would change tab within view models?

I have a NavigationManager to handle changing SwiftUI tab bar selection.
It work if it is set as a #EnvironmentObject in my SwiftUI views, but not when the NavigationManager is called as a service in my view models. The thing is that I would like to use a simpler solution than passing around #EnvironmentObject var navigationManager around and pass them inside view model initializer as I have a lot of them and I am looking for a cleaner approach.
How can I use my NavigationManager to change tabs from inside my view models without passing it in init()?
import SwiftUI
struct ContentView: View {
#StateObject var navigationManager = NavigationManager()
var body: some View {
TabView(selection: $navigationManager.selection) {
AccountView()
.tabItem {
Text("Account")
Image(systemName: "person.crop.circle") }
.tag(NavigationItem.account)
SettingsView()
.tabItem {
Text("Settings")
Image(systemName: "gear") }
.tag(NavigationItem.settings)
.environmentObject(navigationManager)
}
}
}
The navigation manager that I would like to use within view models.
class NavigationManager: ObservableObject {
#Published var selection: NavigationItem = .account
}
enum NavigationItem {
case account
case settings
}
My AccountViewModel and Settings View Model:
class AccountViewModel: ObservableObject {
let navigationManager = NavigationManager()
}
struct AccountView: View {
#StateObject var viewModel = AccountViewModel()
var body: some View {
VStack(spacing: 16) {
Text("AccountView")
.font(.title3)
Button(action: {
viewModel.navigationManager.selection = .settings
}) {
Text("Go to Settings tab")
.font(.headline)
}
.buttonStyle(.borderedProminent)
}
}
}
class SettingsViewModel: ObservableObject {
let navigationManager = NavigationManager()
}
struct SettingsView: View {
#EnvironmentObject var navigationManager: NavigationManager
#StateObject var viewModel = SettingsViewModel()
var body: some View {
VStack(spacing: 16) {
Text("SettingsView")
.font(.title3)
Button(action: {
navigationManager.selection = .account
}) {
Text("Go to Account tab")
.font(.headline)
}
.buttonStyle(.borderedProminent)
}
}
}
I managed to successfully inject my navigationManager as a shared dependency using a property wrapper and change its selection variable using Combine.
So I have created a protocol to wrap NavigationManager to a property wrapper #Injection and make its value a CurrentValueSubject
import Combine
final class NavigationManager: NavigationManagerProtocol, ObservableObject {
var selection = CurrentValueSubject<NavigationItem, Never>(.settings)
}
protocol NavigationManagerProtocol {
var selection: CurrentValueSubject<NavigationItem, Never> { get set }
}
This is the #Injection property wrapper to pass my instance of NavigationManager between .swift files.
#propertyWrapper
struct Injection<T> {
private let keyPath: WritableKeyPath<InjectedDependency, T>
var wrappedValue: T {
get { InjectedDependency[keyPath] }
set { InjectedDependency[keyPath] = newValue }
}
init(_ keyPath: WritableKeyPath<InjectedDependency, T>) {
self.keyPath = keyPath
}
}
protocol InjectedKeyProtocol {
associatedtype Value
static var currentValue: Self.Value { get set }
}
struct InjectedDependency {
private static var current = InjectedDependency()
static subscript<K>(key: K.Type) -> K.Value where K: InjectedKeyProtocol {
get { key.currentValue }
set { key.currentValue = newValue }
}
static subscript<T>(_ keyPath: WritableKeyPath<InjectedDependency, T>) -> T {
get { current[keyPath: keyPath] }
set { current[keyPath: keyPath] = newValue }
}
}
extension InjectedDependency {
var navigationManager: NavigationManagerProtocol {
get { Self[NavigationManagerKey.self] }
set { Self[NavigationManagerKey.self] = newValue }
}
}
private struct NavigationManagerKey: InjectedKeyProtocol {
static var currentValue: NavigationManagerProtocol = NavigationManager()
}
With this in place, I can pass my NavigationManager between my view models and send new value using Combine on button tap:
class AccountViewModel: ObservableObject {
#Injection(\.navigationManager) var navigationManager
}
struct AccountView: View {
var viewModel = AccountViewModel()
var body: some View {
VStack(spacing: 16) {
Text("AccountView")
.font(.title3)
Button(action: {
viewModel.navigationManager.selection.send(.settings)
}) {
Text("Go to Settings tab")
.font(.headline)
}
.buttonStyle(.borderedProminent)
}
}
}
class SettingsViewModel: ObservableObject {
#Injection(\.navigationManager) var navigationManager
}
struct SettingsView: View {
#StateObject var viewModel = SettingsViewModel()
var body: some View {
VStack(spacing: 16) {
Text("SettingsView")
.font(.title3)
Button(action: {
viewModel.navigationManager.selection.send(.account)
}) {
Text("Go to Account tab")
.font(.headline)
}
.buttonStyle(.borderedProminent)
}
}
}
To wrap things up, I inject NavigationManager in my ContentView and use the .onReceive(_:action:) modifier to keep track of the newly selected tab from anywhere in code.
struct ContentView: View {
#Injection(\.navigationManager) var navigationManager
#State var selection: NavigationItem = .account
var body: some View {
TabView(selection: $selection) {
AccountView()
.tabItem {
Text("Account")
Image(systemName: "person.crop.circle") }
.tag(NavigationItem.account)
SettingsView()
.tabItem {
Text("Settings")
Image(systemName: "gear") }
.tag(NavigationItem.settings)
}
.onReceive(navigationManager.selection) { newValue in
selection = newValue
}
}
}

Swiftui connect command to value in main view

Does someone know how to connect commands to the rest of the project?
For example: I want to toggle the AddNew variable in the content view to show the add new item sheet by using the command.
struct SampleApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
.commands {
CommandGroup(after: CommandGroupPlacement.newItem) {
Button("Add new", action: {
self.AddNew.toggle() // should toggle variable in content View
})
}
}
}
}
struct ContentView: View {
#State var AddNew = false
var body: some View {
Button(action: {
self.AddNew.toggle()
}) {
Text("Show Detail")
}.sheet(isPresented: $AddNew) {
AddNew(dimiss: $AddNew)
}
}
}
A solution could be to have a #Published var in a class conforming to ObservableObject.
You would toggle the boolean in the class and access it from wherever you want (as an #EnvironmentObject for example).
Like this:
class AppModel: ObservableObject {
#Published var addNew: Bool = false
}
struct SampleApp: App {
#ObservedObject var model = AppModel()
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(model)
}
.commands {
CommandGroup(after: CommandGroupPlacement.newItem) {
Button("Add new", action: {
self.model.addNew.toggle()
})
}
}
}
}
struct ContentView: View {
#EnvironmentObject var model: AppModel
var body: some View {
Button(action: {
self.model.addNew.toggle()
}) {
Text("Show Detail")
}.sheet(isPresented: $model.addNew) {
AddNew(dimiss: $model.addNew)
}
}
}