My view moves up when I implemented the navigation link in swiftui - swift

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 !!!!

Related

How to hide a swiftUI view by touching anywhere outside of it

I have a view that call an alert that is another smaller view, whenever the second View is shown, I want to hide it when clicking outside of it.
How can I do that?
struct AlertView: View{
let screenSize = UIScreen.main.bounds
#Binding var alertIsShown: Bool
var body: some View{
VStack{
Button("Cancel") {
self.alertIsShown=false
}
}.padding()
.frame(width: screenSize.width * 0.85, height: screenSize.height * 0.6)
.background(Color(red: 0.4627, green: 0.8392, blue: 1.0))
.clipShape(RoundedRectangle(cornerRadius: 20.0, style: .continuous))
.offset(y: alertIsShown ? 0 : screenSize.height)
.animation(.spring())
.shadow(color: Color(.white), radius: 6, x: -9, y: -0)
}
}
}
For the main view that call the alert:
struct MainView: View {
#State private var alertIsShown = false
#State var liveOrdersList: [String] = ["item-1", "item-2"]
var body: some View {
VStack{
NavigationView{
List{
ForEach(liveOrdersList, id: \.self) { order in
HStack {
VStack(alignment: .leading){
Text("\(order.totalPrice)")
}
Spacer()
Button("add") {
withAnimation(.linear(duration: 0.3)) {
alertIsShown.toggle()
}
}
}
}
}
}
}
}
if alertIsShown{ //here I call the aler
AlertView(alertIsShown: $alertIsShown)
}
}
The list of buttons call the alert view.
How can I hide it when tapping outside of it?
You could try this approach, using .simultaneousGesture(...), as shown in this example code, to hide the AlertView by touching anywhere outside of it.
struct ContentView: View {
var body: some View {
MainView()
}
}
struct MainView: View {
#State var alertIsShown = false
// for testing
#State var liveOrdersList: [String] = ["item-1", "item-2", "item-3", "item-4", "item-5"]
var body: some View {
NavigationView {
VStack {
List {
ForEach(liveOrdersList, id: \.self) { order in
HStack {
VStack(alignment: .leading) {
Text("\(order)")
}
Spacer()
Button("add") {
withAnimation(.linear(duration: 0.3)) {
alertIsShown.toggle()
}
}
}
}
}
// -- here
.simultaneousGesture(alertIsShown ? TapGesture().onEnded {
alertIsShown = false
} : nil)
if alertIsShown {
AlertView(alertIsShown: $alertIsShown)
}
}
}
}
}

CocoaMQTT init, Can't seem to connect to MQTT Broker

Please excuse how much of a newbie I am. I started on swift at the beginning of last week...
I am trying to make an app that uses BLE and or MQTT to talk to a raspberry pi through a broker. the BLE side is ok but the MQTT (for when out of BLE range) I'm having trouble with.
This is in a swift file of a couple of classes:
import Foundation
import CocoaMQTT
class MQTTManager{
static let shared = MQTTManager()
private var mqttClient: CocoaMQTT
init() {
let clientID = "swift-Trial-13579"
let host = "IP.Goes.Here"
let port = UInt16(1883)
self.mqttClient = CocoaMQTT(clientID: clientID, host: host, port: port)
self.mqttClient.username = "User"
self.mqttClient.password = "Pass"
self.mqttClient.keepAlive = 60
self.mqttClient.connect()
sendMessage(topic: "app/init", message: "init called")
print("MQTT Init Called")
}
func sendMessage(topic:String, message:String){
self.mqttClient.publish(topic, withString: message)
print("publish MQTT called with message: \(message) and a topic of: \(topic)")
}
}
class useProperties: ObservableObject{
#Published var useMQTT = false
#Published var recallMQTTScene = false
#Published var MQTTScene = 0
init(){
}
}
I then have 3 views, ContentView
import SwiftUI
import CocoaMQTT
struct ContentView: View {
#State public var Connection:Bool = false
#State public var SceneMessqe: String = ""
let MQTTHandle = MQTTManager()
// the main view actually is here
var body: some View{
// SceneButton(function: { self.MQTTPub})
VStack{
HStack {
Text("MultiControl POC")
.font(.title)
//.padding()
}
Spacer()
mainSwitch()
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.environmentObject(BLEManager())
}
}
mainSwitch
import SwiftUI
import CocoaMQTT
struct mainSwitch: View {
#State public var Connection:Bool = false
#State private var selection: String? = nil
#StateObject var bleManager = BLEManager() // gets from environment.
public var properties = useProperties()
#State private var selectDeviceShown = false
#State var isPresenting = false
public var MQTTHandle = MQTTManager()
var body: some View {
VStack (spacing: 0){
NavigationView{
HStack {
Spacer()
NavigationLink(destination: sheetView(), isActive: $isPresenting) { EmptyView() }// added for nav but not working
Menu("Menu") {
Button("BLE Setup", action: {
self.isPresenting = true // added to trigger nav not workinh
print("Setup button pressed")
//selectDeviceShown = true
})
Button("Reconnect", action: {
bleManager.myCentral.connect(bleManager.wrappedControllers[bleManager.currentSceneSwitchControllerUUID]!.wrappedPerh)
})
Button(action: {
MQTTManager.init()
Connection.toggle()
properties.useMQTT = true
print("connect/disconnect pressed useMQTT = \(properties.useMQTT)")
}, label: {
Text(Connection ? "MQTT Disconnect":"MQTT Connect")
})
Button("Cancel", action: {
print("oops")
})
}
.foregroundColor(Connection ? .green : .red)
.padding(38)
.overlay(
RoundedRectangle(cornerRadius: 15)
.stroke(lineWidth: 2)
.foregroundColor(Connection ? .green : .red)
)
Spacer()
Spacer()
}
.fixedSize(horizontal: false, vertical: true)
.frame(maxHeight: 10)
.padding()
}
HStack{
Spacer()
VStack{
Spacer()
SceneButton(sceneName: "Scene 1", sceneNumber: 1)
Spacer()
SceneButton(sceneName: "Scene 3" , sceneNumber: 3)
Spacer()
SceneButton(sceneName: "Scene 5", sceneNumber: 5)
Spacer()
}
Spacer()
VStack{
Spacer()
SceneButton(sceneName: "Scene 2", sceneNumber: 2)
Spacer()
SceneButton(sceneName: "Scene 4", sceneNumber: 4)
Spacer()
SceneButton(sceneName: "Off", sceneNumber: 6)
Spacer()
}
Spacer()
}
Spacer()
}
.environmentObject(useProperties())
.environmentObject(BLEManager())
}
}
struct mainSwitch_Previews: PreviewProvider {
static var previews: some View {
Group {
mainSwitch()
.environmentObject(BLEManager())
.environmentObject(useProperties())
}
}
}
and finally sceneButton
import SwiftUI
import CocoaMQTT
struct SceneButton: View {
var sceneName: String
var sceneNumber: Int
let properties = useProperties()
#State private var isDisabled: Bool = true
#State private var isDuringGesture: Bool = false
#StateObject private var bleManager = BLEManager()
let btnClr:Color = Color.orange
let btnClrOutr:Color = Color.red
let btnPressedClr:Color = Color.gray
let MQTTHandle = MQTTManager()
var body: some View {
if (properties.useMQTT){
Button(sceneName) {
bleManager.writeToCharacteristicButtonPress(peripheral: bleManager.currentSceneSwitchControllerUUID, sceneToGoToo: (sceneNumber).description, setButtonPressed: true) // Note the number is the same number as the button.
bleManager.writeToCharacteristicButtonPress(peripheral: bleManager.currentSceneSwitchControllerUUID, sceneToGoToo: (sceneNumber).description, setButtonPressed: false)
print("BLE Button" + sceneName)
print("\(properties.useMQTT)")
}
.foregroundColor(.white)
.frame(minWidth: 100)
.padding()
.background(Color(red: 1, green: 0.1, blue: 0.1))
.cornerRadius(10)
.padding(10)
.font(.title2)
.font(.system(size: 20))
}else{
Button(sceneName) {
print("\(properties.useMQTT)")
MQTTHandle.sendMessage(topic: "apptest/scene", message: "\(sceneNumber)")
}
.frame(minWidth: 100)
.padding()
.background(Color(red: 0.40, green: 0.60, blue: 0))
.cornerRadius(10)
.padding(10)
}
}
}
struct SceneButton_Previews: PreviewProvider {
static var previews: some View {
SceneButton(sceneName: "Scene X", sceneNumber: 42) //, publishSceneMQTT: sceneHandle
.environmentObject(BLEManager())
.environmentObject(useProperties())
}
}
Currently when the app loads it calls the MQTT init many times, and then doesn't connect reliably, occasionally, maybe 1 in 10 times it connects to send a single message then I can't send more.
Ideally it would only connect when I press the connect button in the menu in mainSwitch. However each button (SceneButton) should publish something slightly different.
Firstly, is it an issue that it keeps calling init at start?
Secondly, is there something visible that I am doing wrong to mean its not reliably connecting?
thirdly, (least important) in sceneButton the button should change whether using BLE or MQTT, this variable, useMQTT is set in the mainSwitch file. but doesn't change in sceneButton, what have I done wrong?
You use everywhere (!) different instances of MQTTManager, because create it via init, instead you should use everywhere MQTTManager.shared, like
struct ContentView: View {
#State public var Connection:Bool = false
#State public var SceneMessqe: String = ""
// let MQTTHandle = MQTTManager() // << not this !!
let MQTTHandle = MQTTManager.shared // << this one !!
so review all your code and fix as above.

AnyTransition issue with simple view update in macOS

I have 2 user view called user1 and user2, I am updating user with button, and I want give a transition animation to update, but for some reason my transition does not work, as I wanted, the issue is there that Text animated correctly but image does not, it stay in its place and it does not move with Text to give a smooth transition animation.
struct ContentView: View {
#State var show: Bool = Bool()
var body: some View {
VStack {
if (show) {
UserView(label: { Text("User 1") })
.transition(AnyTransition.asymmetric(insertion: AnyTransition.move(edge: Edge.trailing), removal: AnyTransition.move(edge: Edge.leading)))
}
else {
UserView(label: { Text("User 2") })
.transition(AnyTransition.asymmetric(insertion: AnyTransition.move(edge: Edge.leading), removal: AnyTransition.move(edge: Edge.trailing)))
}
Button("update") { show.toggle() }
}
.padding()
.animation(Animation.linear(duration: 1.0), value: show)
}
}
struct UserView<Label: View>: View {
let label: () -> Label
#State private var heightOfLabel: CGFloat? = nil
var body: some View {
HStack {
if let unwrappedHeight: CGFloat = heightOfLabel {
Image(systemName: "person")
.resizable()
.frame(width: unwrappedHeight, height: unwrappedHeight)
}
label()
.background(GeometryReader { proxy in
Color.clear
.onAppear(perform: { heightOfLabel = proxy.size.height })
})
Spacer(minLength: CGFloat.zero)
}
.animation(nil, value: heightOfLabel)
}
}
the heightOfLabel doesn't have to be optional, and then it works:
struct UserView<Label: View>: View {
let label: () -> Label
#State private var heightOfLabel: CGFloat = .zero // not optional
var body: some View {
HStack {
Image(systemName: "person")
.resizable()
.frame(width: heightOfLabel, height: heightOfLabel)
label()
.background(GeometryReader { proxy in
Color.clear
.onAppear(perform: { heightOfLabel = proxy.size.height })
})
Spacer(minLength: CGFloat.zero)
}
.animation(nil, value: heightOfLabel)
}
}

Tabbar middle button utility function in SwiftUI

I'm trying to reproduce a "Instagram" like tabBar which has a "Utility" button in the middle which doesn't necessarily belong to the tabBar eco system.
I have attached this gif to show the behaviour I am after. To describe the issue. The tab bar in the middle (Black plus) is click a ActionSheet is presented INSTEAD of switching the view.
How I would do this in UIKit is simply use the
override func tabBar(tabBar: UITabBar, didSelectItem item: UITabBarItem) {
print("Selected item")
}
Function from the UITabBarDelegate. But obviously we can't do this in SwiftUI so was looking to see if there was any ideas people have tried. My last thought would be to simply wrap it in a UIView and use it with SwiftUI but would like to avoid this and keep it native.
I have seen a write up in a custom TabBar but would like to use the TabBar provided by Apple to avoid any future discrepancies.
Thanks!
Edit: Make the question clearer.
Thanks to Aleskey for the great answer (Marked as correct). I evolved it a little bit in addition to a medium article that was written around a Modal. I found it to be a little different
Here's the jist.
A MainTabBarData which is an Observable Object
final class MainTabBarData: ObservableObject {
/// This is the index of the item that fires a custom action
let customActiontemindex: Int
let objectWillChange = PassthroughSubject<MainTabBarData, Never>()
var previousItem: Int
var itemSelected: Int {
didSet {
if itemSelected == customActiontemindex {
previousItem = oldValue
itemSelected = oldValue
isCustomItemSelected = true
}
objectWillChange.send(self)
}
}
func reset() {
itemSelected = previousItem
objectWillChange.send(self)
}
/// This is true when the user has selected the Item with the custom action
var isCustomItemSelected: Bool = false
init(initialIndex: Int = 1, customItemIndex: Int) {
self.customActiontemindex = customItemIndex
self.itemSelected = initialIndex
self.previousItem = initialIndex
}
}
And this is the TabbedView
struct TabbedView: View {
#ObservedObject private var tabData = MainTabBarData(initialIndex: 1, customItemIndex: 2)
var body: some View {
TabView(selection: $tabData.itemSelected) {
Text("First Screen")
.tabItem {
VStack {
Image(systemName: "globe")
.font(.system(size: 22))
Text("Profile")
}
}.tag(1)
Text("Second Screen")
.tabItem {
VStack {
Image(systemName: "plus.circle")
.font(.system(size: 22))
Text("Profile")
}
}.tag(2)
Text("Third Screen")
.tabItem {
VStack {
Image(systemName: "number")
.font(.system(size: 22))
Text("Profile")
}
}.tag(3)
}.actionSheet(isPresented: $tabData.isCustomItemSelected) {
ActionSheet(title: Text("SwiftUI ActionSheet"), message: Text("Action Sheet Example"),
buttons: [
.default(Text("Option 1"), action: option1),
.default(Text("Option 2"), action: option2),
.cancel(cancel)
]
)
}
}
func option1() {
tabData.reset()
// ...
}
func option2() {
tabData.reset()
// ...
}
func cancel() {
tabData.reset()
}
}
struct TabbedView_Previews: PreviewProvider {
static var previews: some View {
TabbedView()
}
}
Similar concept, just uses the power of SwiftUI and Combine.
You could introduce new #State property for storing old tag of presented tab. And perform the next method for each of your tabs .onAppear { self.oldSelectedItem = self.selectedItem } except the middle tab. The middle tab will be responsible for showing the action sheet and its method will look the following:
.onAppear {
self.shouldShowActionSheet.toggle()
self.selectedItem = self.oldSelectedItem
}
Working example:
import SwiftUI
struct ContentView: View {
#State private var selectedItem = 1
#State private var shouldShowActionSheet = false
#State private var oldSelectedItem = 1
var body: some View {
TabView (selection: $selectedItem) {
Text("Home")
.tabItem { Image(systemName: "house") }
.tag(1)
.onAppear { self.oldSelectedItem = self.selectedItem }
Text("Search")
.tabItem { Image(systemName: "magnifyingglass") }
.tag(2)
.onAppear { self.oldSelectedItem = self.selectedItem }
Text("Add")
.tabItem { Image(systemName: "plus.circle") }
.tag(3)
.onAppear {
self.shouldShowActionSheet.toggle()
self.selectedItem = self.oldSelectedItem
}
Text("Heart")
.tabItem { Image(systemName: "heart") }
.tag(4)
.onAppear { self.oldSelectedItem = self.selectedItem }
Text("Profile")
.tabItem { Image(systemName: "person.crop.circle") }
.tag(5)
.onAppear { self.oldSelectedItem = self.selectedItem }
}
.actionSheet(isPresented: $shouldShowActionSheet) { ActionSheet(title: Text("Title"), message: Text("Message"), buttons: [.default(Text("Option 1"), action: option1), .default(Text("Option 2"), action: option2) , .cancel()]) }
}
func option1() {
// do logic 1
}
func option2() {
// do logic 2
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Previous answers did not help me so I'm pasting my complete solution.
import SwiftUI
import UIKit
enum Tab {
case map
case recorded
}
#main
struct MyApp: App {
#State private var selectedTab: Tab = .map
#Environment(\.scenePhase) private var phase
var body: some Scene {
WindowGroup {
VStack {
switch selectedTab {
case .map:
NavigationView {
FirstView()
}
case .recorded:
NavigationView {
SecondView()
}
}
CustomTabView(selectedTab: $selectedTab)
.frame(height: 50)
}
}
}
}
struct FirstView: View {
var body: some View {
Color(.systemGray6)
.ignoresSafeArea()
.navigationTitle("First view")
}
}
struct SecondView: View {
var body: some View {
Color(.systemGray6)
.ignoresSafeArea()
.navigationTitle("second view")
}
}
struct CustomTabView: View {
#Binding var selectedTab: Tab
var body: some View {
HStack {
Spacer()
Button {
selectedTab = .map
} label: {
VStack {
Image(systemName: "map")
.resizable()
.scaledToFit()
.frame(width: 25, height: 25)
Text("Map")
.font(.caption2)
}
.foregroundColor(selectedTab == .map ? .blue : .primary)
}
.frame(width: 60, height: 50)
Spacer()
Button {
} label: {
ZStack {
Circle()
.foregroundColor(.secondary)
.frame(width: 80, height: 80)
.shadow(radius: 2)
Image(systemName: "plus.circle.fill")
.resizable()
.foregroundColor(.primary)
.frame(width: 72, height: 72)
}
.offset(y: -2)
}
Spacer()
Button {
selectedTab = .recorded
} label: {
VStack {
Image(systemName: "chart.bar")
.resizable()
.scaledToFit()
.frame(width: 25, height: 25)
Text("Recorded")
.font(.caption2)
}
.foregroundColor(selectedTab == .recorded ? .blue : .primary)
}
.frame(width: 60, height: 50)
Spacer()
}
}
}

How can I use Navigation in alert using SwiftUI

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
}
}
}