I'm working on a Bluetooth Application.It has onboarding and dashboard.On the Onboarding there is pairing and instructions on how to use the module, and the dashboard controls the peripheral device.So I need to unpair using an alert and navigate it to a different page called Onboarding.How can I navigate to a different view using an alert.
Code Block
import SwiftUI
import BLE
struct Dashboard: View {
#EnvironmentObject var BLE: BLE
#State private var showUnpairAlert: Bool = false
#State private var hasConnected: Bool = false
let defaults = UserDefaults.standard
let defaultDeviceinformation = "01FFFFFFFFFF"
struct Keys {
static let deviceInformation = "deviceInformation"
}
var body: some View {
VStack(alignment: .center, spacing: 0) {
// MARK: - Menu Bar
HStack(alignment: .center, spacing: 10) {
VStack(alignment: .center, spacing: 4) {
Text(self.hasConnected ? "PodId \(checkForDeviceInformation())":"Pod is not connected")
.font(.footnote)
.foregroundColor(.white)
Button(action: {
print("Unpair tapped!")
self.showUnpairAlert = true
}) {
HStack {
Text("Unpair")
.fontWeight(.bold)
.font(.body)
}
.frame(minWidth: 85, minHeight: 35)
.foregroundColor(.white)
.background(Color(red: 0.8784313725490196, green: 0.34509803921568627, blue: 0.36470588235294116))
.cornerRadius(30)
}
}
}
}
.alert(isPresented: $showUnpairAlert) {
Alert(title: Text("Unpair from \(checkForDeviceInformation())"), message: Text("Do you want to unpair the current pod?"), primaryButton: .destructive(Text("Unpair")) {
self.unpairAndSetDefaultDeviceInformation()
}, secondaryButton: .cancel())
}
}
func checkForDeviceInformation() -> String {
let deviceInformation = defaults.value(forKey: Keys.deviceInformation) as? String ?? ""
print("Device Info \(deviceInformation)")
return deviceInformation
}
func unpairAndSetDefaultDeviceInformation() {
defaults.set(defaultDeviceinformation, forKey: Keys.deviceInformation)
print("Pod unpaired and view changed to Onboarding")
}
}
Thank you !!!!
I simplified your code snapshot for demo, but think the idea would be clear
struct TestNavigationFromAlert: View {
#State private var showUnpairAlert: Bool = false
#State private var activateLink: Bool = false
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: Text("Your Onboarding page"), isActive: $activateLink,
label: { EmptyView() })
// DEMO BUTTON - REMOVE IT
Button(action: { self.showUnpairAlert = true }) { Text("Alert") }
// YOUR CODE IS HERE
}
.alert(isPresented: $showUnpairAlert) {
Alert(title: Text("Unpair from \(checkForDeviceInformation())"), message: Text("Do you want to unpair the current pod?"), primaryButton: .destructive(Text("Unpair")) {
self.unpairAndSetDefaultDeviceInformation()
}, secondaryButton: .cancel())
}
}
}
func checkForDeviceInformation() -> String {
// YOUR CODE IS HERE
return "Stub information"
}
func unpairAndSetDefaultDeviceInformation() {
// YOUR CODE IS HERE
DispatchQueue.main.async {
self.activateLink = true
}
}
}
Related
I am creating an app in which there is a list of users, with an associated button for each user that leads to another screen. This screen should display all the data of the selected user.
struct UserList: View {
#StateObject var administratorManager = AdministratorManager()
#State var ricerca = ""
#State var isPresented: Bool = false
var body: some View {
ScrollView(.vertical, showsIndicators: false) {
ForEach(administratorManager.users.sorted(using: [
KeyPathComparator(\.surname, order: .forward)
])) { user in
if user.toCheck.contains(where: {$0.range(of: ricerca.lowercased()) != nil}) || user.toCheck.contains(where: {$0.range(of: ricerca.capitalized) != nil}) || ricerca.isEmpty {
VStack(alignment: .leading) {
Text(user.number).font(.subheadline)
Text(user.name).font(.subheadline)
Text(user.surname).font(.subheadline)
}.background(Color("Purple"))
.cornerRadius(5)
// Button to change screen ---------------------------------------------------------------------------------------------------
Button {
isPresented.toggle()
administratorManager.infoUtente[2] = user.number
} label: {
Text("Tap me!")
.padding()
.background(Color.white)
.cornerRadius(20)
}.padding(1)
// ---------------------------------------------------------------------------------------------------------------------------
}
}.navigationBarTitle("User List")
.searchable(text: $ricerca, prompt: "Search")
.onAppear() {
administratorManager.fetchData(collection: "Users")
administratorManager.checkPermission()
}
.alert(isPresented: $administratorManager.isMessage) {
Alert(title: Text(administratorManager.title), message: Text(administratorManager.message),
dismissButton: .default(Text("Ho capito!")))
}
.sheetWithDetents(
isPresented: $isPresented,
detents: [.medium(),.large()]
) {
print("The sheet has been dismissed")
} content: {
Group {
userInfo()
}
.font(.title)
}
}
}
}
This is the screen where the user information will be displayed:
struct userInfo: View {
// Oggetti.
#StateObject var administratorManager = AdministratorManager() // Oggetto riguardante i comandi riservati agli admin.
var body: some View {
HStack {
Text("Nome: \(administratorManager.infoUtente[0])")
.padding(.horizontal, 5)
Text("Cogome: \(administratorManager.infoUtente[1])")
.padding(.horizontal, 5)
}.padding(1)
Text("Numero: \(administratorManager.infoUtente[2])")
.padding(.horizontal, 5)
Button("print", action: {
print(administratorManager.infoUtente[2])
}).padding(1)
}
}
If I print in the console the value of administratorManager.infoUser [2] is empty.
I've recently been using Swift, and I haven't found anything to fix it. How can I solve?
You have two different AdministratorManager instances. One in UserList, second in userInfo. They are not connected and have different data.
Change to this in userInfo
#EnvironmentObject var administratorManager: AdministratorManager
And in UserList pass environment object to next screen
Group {
userInfo()
.environmentObject(administratorManager)
}
Currently since its in a ForEach loop and i only have one State var, when i press follow they all change to following. I used a ternary operator to make the button color and text switch if the isFollowing is toggled. How would i go about making it toggle only for a specified user when the button is clicked. Would i just need to make 3 buttons outside the loop? When i used user.isFollowingUser.toggle in the button it tells me that I cant mutate user.
import SwiftUI
struct InstagramModel: Identifiable{
let id: String = UUID().uuidString
let username: String
let userImage: String
let followers: Int
let isVerified: Bool
let isFollowingUser: Bool
}
struct ModelPractice: View {
#State private var users: [InstagramModel] = [
InstagramModel(username: "aleedagenie", userImage: "AliImage", followers: 490, isVerified: true, isFollowingUser: false),
InstagramModel(username: "nicole29", userImage: "volcano2", followers: 1090, isVerified: true, isFollowingUser: false),
InstagramModel(username: "shamat81", userImage: "crashedCar", followers: 290, isVerified: false, isFollowingUser: false)
]
#State private var isFollowing: Bool = false
#State private var isShowDialog: Bool = false
#State private var background: Color = .mint
var body: some View {
NavigationView{
VStack{
List{
Section {
ForEach(users) { user in
VStack(alignment: .leading) {
HStack {
Image(user.userImage)
.resizable()
.frame(width: 35, height: 35)
.clipShape(Circle())
VStack(alignment: .leading) {
Text(user.username)
.font(.headline)
HStack {
Text("Followers:")
.font(.caption)
Text("\(user.followers)")
.font(.caption)
}
}
Spacer()
if user.isVerified{
Image(systemName: "checkmark.seal.fill")
.foregroundColor(.blue)
}
}
Button {
isFollowing.toggle()
} label: {
Text(isFollowing ? "Following" : "Follow")
.foregroundColor(isFollowing ? .black: .white)
.frame(maxWidth: 90)
.background(isFollowing ? .white: .blue)
.cornerRadius(12)
}
.padding(.horizontal, 44)
}
}
} header: {
Text("Instagram Users")
}
.listRowBackground(background)
}
Button {
isShowDialog.toggle()
} label: {
Text("Change Page Style")
.bold()
.frame(maxWidth: 140)
.background(.orange)
.cornerRadius(20)
}
.confirmationDialog("Text", isPresented: $isShowDialog, actions: {
Button {
background = .yellow
} label: {
Text("Option 1")
}
Button {
background = .gray
} label: {
Text("Option 2")
}
Button {
background = .green
} label: {
Text("Option 3")
}
})
.navigationTitle("Instagram")
}
}
}
}
struct ModelPractice_Previews: PreviewProvider {
static var previews: some View {
ModelPractice()
}
}
The problem here is you are trying to mutate the variable of closure called user. user is a temporary variable which is not linked with your users, so you can't mutate it.
Instead you should mutate the users.
Here is my demo, try it out. Code is below the image:
struct UserModel: Identifiable {
var id = UUID()
var name: String = ""
var following = false
}
struct DemoView: View {
#State var listUser = [
UserModel(name: "Lamb Chop", following: false),
UserModel(name: "Steak", following: false)
]
var body: some View {
List {
ForEach(listUser.indices) { index in
HStack {
Text(listUser[index].name)
Button {
listUser[index].following.toggle()
} label: {
Text(listUser[index].following ? "following" : "follow")
}
.padding(5)
.background(.black)
.cornerRadius(15)
}
}
}
}
}
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()
}
}
}
So I am trying to keep my current up at iOS 13 + while bring in a few iOS 14 features.
One of the new features is monitoring if a user loses network connection.
However I am getting a strange error when I run the following code.
TaView.swift (default view)
import SwiftUI
struct TaView: View {
#StateObject var monitor = Monitor()
#ObservedObject var location = LocationManager()
#State var errorDetail = false
var lat: String{
return "\(location.lastKnownLocation?.coordinate.latitude ?? 0.0)"
}
var lon: String{
return "\(location.lastKnownLocation?.coordinate.longitude ?? 0.0)"
}
var state: String{
return "\(UserSettings().state)"
}
init() {
// print(self.data.connected)
self.location.startUpdating()
}
#ObservedObject var userSettings = UserSettings()
var body: some View {
if (self.lat == "0.0" && self.lon == "0.0"){
LocationDisabled()
}
else{
if #available(iOS 14.0, *) {
TabView {
ContentView()
.tabItem {
Image(systemName: "dot.radiowaves.left.and.right")
Text("Radio")
}
WinView()
.tabItem {
Image(systemName: "w.circle")
Text("Win")
}
SettingsView()
.tabItem {
Image(systemName: "gear")
Text("Settings")
}
}.accentColor(Color.red)
.onAppear(){
Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { (timer) in
if monitor.score == 0{
DispatchQueue.main.asyncAfter(deadline: .now()) {
self.errorDetail = true
}
}
//print("this is the score \(monitor.score)")
}
}
.fullScreenCover(isPresented: self.$errorDetail, content: NetworkOutageView.init)
} else {
TabView {
ContentView()
.tabItem {
Image(systemName: "dot.radiowaves.left.and.right")
Text("Radio")
}
WinView()
.tabItem {
Image(systemName: "w.circle")
Text("Win")
}
SettingsView()
.tabItem {
Image(systemName: "gear")
Text("Settings")
}
}.accentColor(Color.red)
.onAppear(){
Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { (timer) in
if monitor.score == 0{
DispatchQueue.main.asyncAfter(deadline: .now()) {
self.errorDetail = true
}
}
//print("this is the score \(monitor.score)")
}
}
.sheet(isPresented: self.$errorDetail, content: NetworkOutageView.init)
}
}
}
}
struct LocationDisabled: View {
#ObservedObject var location = LocationManager()
init() {
self.location.startUpdating()
}
var body: some View {
GeometryReader { geo in
VStack{
Spacer().frame(maxHeight: 100)
Image(systemName: "location.fill").resizable().scaledToFit().frame(width: 100).foregroundColor(Color.white)
VStack{
Text("Enable Location").font(.system(.largeTitle, design: .rounded)).bold().multilineTextAlignment(.leading).foregroundColor(Color.white)
Text("You'll need to enable your location.\nIn order to use access these features").fontWeight(.light).multilineTextAlignment(.center).fixedSize(horizontal: false, vertical: true).padding(.all, 8).foregroundColor(Color.white)
Text("\u{2022} Win Prizes\n\n\u{2022} Change Stations\n\n\u{2022} Access Podcasts\n\n\u{2022} Request Songs\n\n\u{2022} And More").bold().multilineTextAlignment(.center).fixedSize(horizontal: false, vertical: true).padding(.all, 8).foregroundColor(Color.white)
Spacer()
Button(action: {
self.location.requestLoc()
}) {
Text("ALLOW LOCATION")
.font(.headline)
.bold()
}.buttonStyle(LocationGradientButtonStyle())
.padding(.bottom, 100)
}
// ImageOverlay()
}.frame(maxWidth: .infinity,maxHeight: .infinity).edgesIgnoringSafeArea(.all).background(
Image("TooFarWallPaper").resizable().aspectRatio(contentMode: .fill).frame(maxWidth: .infinity,maxHeight: .infinity).edgesIgnoringSafeArea(.all)
)
}
}
}
struct LocationGradientButtonStyle: ButtonStyle {
func makeBody(configuration: Self.Configuration) -> some SwiftUI.View {
configuration.label
.foregroundColor(Color.white)
.padding()
.background(LinearGradient(gradient: Gradient(colors: [Color.blue, Color.purple]), startPoint: .topLeading, endPoint: .bottomTrailing))
.cornerRadius(15.0)
}
}
struct SaveGradientButtonStyle: ButtonStyle {
func makeBody(configuration: Self.Configuration) -> some SwiftUI.View {
configuration.label
.foregroundColor(Color.black)
.padding()
.background(LinearGradient(gradient: Gradient(colors: [Color.green, Color.green]), startPoint: .topLeading, endPoint: .bottomTrailing))
.cornerRadius(15.0)
}
}
Monitor.swift
import Network
import SwiftUI
// An enum to handle the network status
enum NetworkStatus: String {
case connected
case disconnected
}
class Monitor: ObservableObject {
private let monitor = NWPathMonitor()
private let queue = DispatchQueue(label: "Monitor")
#Published var score = 0
#Published var status: NetworkStatus = .connected
init() {
monitor.pathUpdateHandler = { [weak self] path in
guard let self = self else { return }
// Monitor runs on a background thread so we need to publish
// on the main thread
DispatchQueue.main.async {
if path.status == .satisfied {
print("We're connected!")
self.status = .connected
self.score = 1
} else {
print("No connection.")
self.score = 0
self.status = .disconnected
}
}
}
monitor.start(queue: queue)
}
}
The error is
Thread 1: signal SIGABRT
I am wondering how do I fix this?
Changing #StateObject to ObservedObject works great.
Note: you don't always need to write the same code again in if and else when checking for #availability (when doing backwards compatibility), you can just create one single variable for a View and reuse it, like in this case we reuse tabViewContent.
struct TaView: View {
#ObservedObject var monitor = Monitor()
#ObservedObject var location = LocationManager()
#State var errorDetail = false
var lat: String{
return "\(location.lastKnownLocation?.coordinate.latitude ?? 0.0)"
}
var lon: String{
return "\(location.lastKnownLocation?.coordinate.longitude ?? 0.0)"
}
var state: String{
return "\(UserSettings().state)"
}
init() {
// print(self.data.connected)
self.location.startUpdating()
}
#ObservedObject var userSettings = UserSettings()
var tabViewContent: some View {
TabView {
ContentView()
.tabItem {
Image(systemName: "dot.radiowaves.left.and.right")
Text("Radio")
}
WinView()
.tabItem {
Image(systemName: "w.circle")
Text("Win")
}
SettingsView()
.tabItem {
Image(systemName: "gear")
Text("Settings")
}
}.accentColor(Color.red)
.onAppear(){
Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { (timer) in
if monitor.score == 0{
self.errorDetail = true
} else {
self.errorDetail = false
}
//print("this is the score \(monitor.score)")
}
}
}
var body: some View {
if (self.lat == "0.0" && self.lon == "0.0"){
LocationDisabled()
}
else{
if #available(iOS 14.0, *) {
tabViewContent
.fullScreenCover(isPresented: self.$errorDetail, content: NetworkOutageView.init)
} else {
tabViewContent
.sheet(isPresented: self.$errorDetail, content: NetworkOutageView.init)
}
}
}
}
Tested with Xcode 12.4, on iOS13.3.1 and iOS 14.4.2
Mockup of the Application
Problem:
My application successfully navigates from one view to another without any complexities.When I use the navigationLink to navigate from View 4 to View 2 (refer mockup). The view 2 movesup. I tried debugging but I found no solution.
I have designed a mockup of what I am trying to acheive.
Code Block for View 4:
import SwiftUI
import BLE
struct View4: View {
#EnvironmentObject var BLE: BLE
#State private var showUnpairAlert: Bool = false
#State private var hasConnected: Bool = false
#State private var activateLink: Bool = false
let defaults = UserDefaults.standard
let defaultDeviceinformation = "01FFFFFFFFFF"
struct Keys {
static let deviceInformation = "deviceInformation"
}
var body: some View {
VStack(alignment: .center, spacing: 0) {
NavigationLink(destination: View2(), isActive: $activateLink,label: { EmptyView() })
// MARK: - Menu Bar
HStack(alignment: .center, spacing: 10) {
VStack(alignment: .center, spacing: 4) {
Text(self.hasConnected ? "PodId \(checkForDeviceInformation())":"Pod is not connected")
.font(.footnote)
.foregroundColor(.white)
Button(action: {
print("Unpair tapped!")
self.showUnpairAlert = true
}) {
HStack {
Text("Unpair")
.fontWeight(.bold)
.font(.body)
}
.frame(minWidth: 85, minHeight: 35)
.foregroundColor(.white)
.background(Color(red: 0.8784313725490196, green: 0.34509803921568627, blue: 0.36470588235294116))
.cornerRadius(30)
}
}
}
}
.alert(isPresented: $showUnpairAlert) {
Alert(title: Text("Unpair from \(checkForDeviceInformation())"), message: Text("Do you want to unpair the current pod?"), primaryButton: .destructive(Text("Unpair")) {
self.unpairAndSetDefaultDeviceInformation()
}, secondaryButton: .cancel())
}
}
func checkForDeviceInformation() -> String {
let deviceInformation = defaults.value(forKey: Keys.deviceInformation) as? String ?? ""
print("Device Info \(deviceInformation)")
return deviceInformation
}
func unpairAndSetDefaultDeviceInformation() {
defaults.set(defaultDeviceinformation, forKey: Keys.deviceInformation)
print("Pod unpaired and view changed to Onboarding")
DispatchQueue.main.async {
self.activateLink = true
}
}
}
Thank you !!!!