I have created a View with Text, Image and Navigation Button in SwiftUI.When navigation button is pressed it will navigate to another view which contains Toggle.When I change the Toggle Value , I also want to change the Text value in the previous View.
Value is getting updated when changing the toggle but didn't reflected when accessed in previous View.
//BluetoothView.swift
struct BluetoothView: View {
#ObjectBinding var bluetooth = Settings()
var body: some View {
return NavigationButton(destination: ToggleBluetoothView()) {
HStack() {
Image("default")
.resizable()
.cornerRadius(12)
.frame(width: 25, height: 25)
.clipped()
.aspectRatio(contentMode: .fit)
Text("Bluetooth")
.color(.blue)
.font(.system(size: 18))
Text(bluetooth.isBluetoothOn ? "On" : "Off")
.color(.gray)
.font(.subheadline)
.frame(width: 50, height: 40, alignment: .trailing)
}
}
}
}
//ToggleBluetoothView.swift
struct ToggleBluetoothView: View {
#ObjectBinding var bluetooth = Settings()
var body: some View {
Form {
Section(header: Text("ENABLE TO CONNECT WITH NEARBY DEVICES")) {
Toggle(isOn: $bluetooth.isBluetoothOn) {
Text("Bluetooth")
}
}
}
}
}
//Settings.swift
class Settings: BindableObject {
var didChange = PassthroughSubject<Void, Never>()
var isBluetoothOn = false { didSet { update() } }
func update() {
didChange.send(())
}
}
You are instantiating Settings separately in each view. Both views need to be seeing the same Settings object:
Change the following:
NavigationButton(destination: ToggleBluetoothView(bluetooth: bluetooth)) { ... }
and remove the initial value in ToggleBluetoothView:
struct ToggleBluetoothView: View {
#ObjectBinding var bluetooth: Settings
var body: some View {
Form {
Section(header: Text("ENABLE TO CONNECT WITH NEARBY DEVICES")) {
Toggle(isOn: $bluetooth.isBluetoothOn) {
Text("Bluetooth")
}
}
}
}
}
Related
I'm giving my first steps with SwiftUI and I'm having problems with a component shown depending on a condition.
I'm trying to show a fullscreen popup (full screen with semi transparent black background and the popup in the middle with white background). To achieve this I've made this component:
struct CustomUiPopup: View {
var body: some View {
ZStack {
}
.overlay(CustomUiPopupOverlay, alignment: .top)
.zIndex(1)
}
private var CustomUiPopupOverlay: some View {
ZStack {
Spacer()
ZStack {
Text("POPUP")
.padding()
}
.zIndex(1)
.frame(width: UIScreen.main.bounds.size.width - 66)
.background(Color.white)
.cornerRadius(8)
Spacer()
}
.frame(width: UIScreen.main.bounds.size.width, height: UIScreen.main.bounds.size.height)
.background(Color.black.opacity(0.6))
}
}
If I set this in my main view, the popup is shown correctly over the button:
struct MainView: View {
var body: some View {
CustomUiPopup()
Button("Click to show popup") {
print("click on button")
}
}
}
If I set this, my popup is not shown (correct because hasToShowPopup is false), but if I click on the button it fails, the popup is not shown and the button can not be clicked again (?!), it seems like the view was freezed.
struct MainView: View {
#State private var hasToShowPopup = false
var body: some View {
if hasToShowPopup {
CustomUiPopup()
}
Button("Click to show popup") {
hasToShowPopup = true
}
}
}
I've even tried to initializate hasToShowPopup to true but the popup keeps failing, it's not shown in the first place:
struct MainView: View {
#State private var hasToShowPopup = true
var body: some View {
if hasToShowPopup {
CustomUiPopup()
}
Button("Click to show popup") {
hasToShowPopup = true
}
}
}
So my conclusion is that, I don't know why, but if I put my CustomUiPopup inside an "if" something is not rendered correctly.
What is wrong with my code?
Anyway, if this is not the correct approach to show a popup, I'll be glad to have any advice.
Following Ptit Xav suggestion I've tried this with the same results (my CustomUiPopup doesn't show):
struct MainView: View {
#State private var hasToShowPopup = false
var body: some View {
VStack {
if hasToShowPopup {
CustomUiPopup()
}
Button("Click to show popup") {
hasToShowPopup = true
}
}
}
}
This works fine with me:
struct CustomUiPopup: View {
var body: some View {
ZStack {
Spacer()
Text("POPUP")
.padding()
.zIndex(1)
.frame(width: UIScreen.main.bounds.size.width - 66)
.background(Color.white)
.cornerRadius(8)
Spacer()
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(
Color.black.opacity(0.6)
.ignoresSafeArea()
)
}
}
struct ContentView: View {
#State private var hasToShowPopup = false
var body: some View {
ZStack {
Button("Click to show popup") {
hasToShowPopup = true
}
if hasToShowPopup {
CustomUiPopup()
}
}
}
}
I'm a SwiftUI trainee. On this particular view below there is an issue like in the image.
While .ignoreSafeArea(.bottom) or .edgesIgnoreSafeArea(.bottom) works on preview.
It does not work on the simulator. I would like to learn is it a bug or am I missing something. Thanks for your help ahead!
Issue screen shot
Updated Solution
The problem was caused by root view logic. When you use navigationLink to navigate another screen on root view causes this problem. I didnt want to use standard NavigationLink to navigate because it was freezing animations(Lottie) I'm playing on screen when you go to some screen via navigationLink and come back.
Below is the view code. Hopefully not that messy.
import SwiftUI
struct ChatView: View {
// MARK: properties
let userName : String
let userImageUrl : String
#ObservedObject var viewModel : ChatViewModel = ChatViewModel()
#ObservedObject var appState : NavigationController = NavigationController.shared
// MARK: body
var body: some View {
ZStack(alignment: .bottom) {
VStack {
buildNavigationBar()
buildMessages()
} // end of Vstack
.ignoresSafeArea(edges:.bottom)
buildInputRow()
} // end of Zstack
.ignoresSafeArea(edges:.bottom)
}
fileprivate func buildInputRow() -> some View {
return
HStack(alignment: .center){
DynamicHorizontalSpacer(size: 30)
Button {
} label: {
Image(systemName: "photo.circle.fill")
.font(.system(size: 35))
}
DynamicHorizontalSpacer(size: 25)
UnobscuredTextFieldView(textBinding: .constant("Hello"), promptText: "Type!", width: 180, color: .white)
DynamicHorizontalSpacer(size: 25)
Button {} label: {
Image(systemName: "paperplane.fill")
.font(.system(size: 30))
.foregroundColor(.accentColor)
}
Spacer()
} // end of HStack
.frame(width: .infinity, height: 100, alignment: .center)
.background(Color.gray.opacity(0.4).ignoresSafeArea(edges:.bottom))
}
fileprivate func buildMessages() -> some View {
return ScrollView(showsIndicators: false) {
ForEach(0...50, id : \.self) { index in
ChatTileView(index: index)
}
.padding(.horizontal,5)
} // end of scrollview
.ignoresSafeArea(edges:.bottom)
}
fileprivate func buildNavigationBar() -> ChatViewNavigationBar {
return ChatViewNavigationBar(userImageUrl: self.userImageUrl, userName: self.userName) {
appState.appState = .Home
}
}
}
fileprivate func buildMessageBox() -> some View {
return HStack(alignment: .center) {
Text(
"""
Fake message
"""
)
.font(.system(size:11))
.foregroundColor(.white)
}
}
}
fileprivate extension View {
func messageBoxModifier(index : Int) -> some View {
self
.multilineTextAlignment(index.isMultiple(of: 2) ?.trailing : .leading)
.frame(minHeight: 30)
.padding(.vertical,7)
.padding(.horizontal,10)
.background(index.isMultiple(of: 2) ? Color.green : Color.mint)
.cornerRadius(12)
.shadow(color: .black.opacity(0.3), radius: 5, y: 5)
}
}
some components used in eg. DynamicHorizantalSpacer
DynamicHorizontalSpacer && Vertical as well they share same logic
struct DynamicVerticalSpacer: View {
let size : CGFloat?
var body: some View {
Spacer()
.frame(width: 0, height: size ?? 20, alignment: .center)
}
}
TextField that I'm using.
struct UnobscuredTextFieldView: View {
#Binding var textBinding : String
let promptText: String
let width : CGFloat
let color : Color
var body: some View {
TextField(text: $textBinding, prompt: Text(promptText)) {
Text("Email")
}
.textFieldModifier()
.modifier(RoundedTextFieldModifier(color:color ,width: width))
}
}
fileprivate extension TextField {
func textFieldModifier() -> some View {
self
.textCase(.lowercase)
.textSelection(.disabled)
.disableAutocorrection(true)
.textInputAutocapitalization(.never)
.textContentType(.emailAddress)
}
}
The problem was caused by root view logic. When you use navigationLink to navigate another screen on root view causes this problem. I didnt want to use standard NavigationLink to navigate because it was freezing animations I'm playing on screen when you go to some screen via navigationLink and come back.
I am very new to iOS development and Swift UI. I am making an app for our company. I believe Apple Notes like approach is best. I got most working tanks to some Udemmy courses and a couple of weeks of intense Googling. But I can't figure out how to implement the toggle sidebar button. I am probably searching for something obvious but using the wrong terminology.
I am talking about this:
When I remove most of the code, I have a structure like this:
NavigationView {
List {
Section(header: RoomHeader()) {
ForEach(sections) { section in
NavigationLink(destination: ViewRoom(section: section)) {
RoomListItem(section: section)
}
}
}
}
.navigationTitle("Rooms")
.listStyle(InsetGroupedListStyle())
}
The ViewRoom class
import SwiftUI
struct ViewRoom: View {
var room: RoomModel
var body: some View {
ZStack {
ScrollView {
VStack {
controls
title
// ....
}
.padding()
}
bottomBar
}
.navigationTitle(room.name)
.navigationBarItems(
trailing: HStack {
// ...
}
)
}
var controls: some View {
HStack {
Spacer()
// Couldn't find the icon on SF Symbols but this is the toggle button
Button(action: {}, label: {
Image(systemName: "rectangle.portrait.arrowtriangle.2.outward")
})
}
.font(.system(size: 24))
.padding(.top, 15)
}
// ...
}
I'd appreciate it if you could let me know if how to implement this toggle feature.
You can use Zstack & animation & transition features of SwiftUI. I made a sample for you to dig more into it and explore more about above mentioned concepts.
struct ContentView: View {
#State private var showDetails = false
var body: some View {
ZStack {
if showDetails {
LeftView()
.transition(.move(edge: .leading))
.zIndex(1)
} else {
Button("Press to show details") {
withAnimation(.spring()) {
showDetails.toggle()
}
}
}
}
.onTapGesture {
withAnimation(.spring()) {
showDetails.toggle()
}
}
}
}
Below is leftView which will animated from left
struct LeftView: View {
var body: some View {
GeometryReader { geometry in
VStack {
Text("Hello, World!")
}
.frame(width: 200, height: geometry.size.height)
.background(Color.blue)
}
}
}
I'm a bit beginner in SWIFT and right now I'm facing a problem whit UI. Let me try to explain my problem.
my homeview screen data coming from web service using Observable object and it loads the data first time. But when I tried to open my left side slide menus than homeView webservice/obervable object data is just cleared when open the left slide menu view. Why my observable object data is empty. Let me share my code:
1.------ This is a my main/parentView
struct ContentView: View {
#EnvironmentObject var viewRouter: ViewRouter
var body: some View {
let drag = DragGesture()
.onEnded {
if $0.translation.width < -100 {
withAnimation {
self.viewRouter.showSlideOutMenu = false
self.viewRouter.showDepartmentsMenu = false
self.viewRouter.showAccountMenu = false
}
}
}
return GeometryReader { g in
ZStack(alignment: .leading) {
RouteChanger(viewRouter: self._viewRouter)
if self.viewRouter.showSlideOutMenu {
MainMenuView(viewRouter: self._viewRouter)
.frame(width: g.size.width/2)
.transition(.move(edge: .leading))
}
}
.gesture(drag)
}
}
}
2.----- This is my RouteChanger view for navigate to different pages of my views.
struct RouteChanger: View {
#EnvironmentObject var viewRouter: ViewRouter
var body: some View {
GeometryReader { g in
VStack {
if self.viewRouter.currentPage == "Home" {
HomeView()
//.modifier(PageSwitchModifier())
}
}
}
}
}
3.... This is my homeView where I am using Observeable Object
struct HomeView: View {
#ObservedObject var homeController = HomeController()
var body: some View {
GeometryReader { g in
ZStack {
Color(UIColor.midTown.blue)
.edgesIgnoringSafeArea(.top)
VStack { //whole body
if self.homeController.homePageData.CODE == "0" {
ImageViewWidget(imageUrl: (self.homeController.homePageData.DATA?.headerList[0].img_url)!)
.frame(minWidth: g.size.width, maxWidth: g.size.width, minHeight: (g.size.width * UIImage(named: "header")!.size.height) / UIImage(named: "header")!.size.width, maxHeight: (g.size.width * UIImage(named: "header")!.size.height) / UIImage(named: "header")!.size.width)
}
else {
Text("Loading...")
.foregroundColor(Color.blue)
.padding()
.frame(width: g.size.width)
}
}
}
}
}
}
The EnvironmentObject is injected for all subviews automatically, so related part of your ContentView should look like below
ZStack(alignment: .leading) {
RouteChanger() // << here
if self.viewRouter.showSlideOutMenu {
MainMenuView() // << here
.frame(width: g.size.width/2)
.transition(.move(edge: .leading))
}
Is there a way to reload a swiftUI view from its hosting controller?
I have a WKHostingController with a swiftUI view for its body. When notification outside of the view gets fired, I need to update the view. If I try to change a #Binding or #State variable, I'm getting error
I can't update values outside of the view
But I can't find a refresh method.
Any ideas?
Hosting Controller:
class WorkoutListInterfaceController : WKHostingController<WorkoutListView> {
override var body: WorkoutListView {
WorkoutListView(host: self)
}
override func didAppear() {
// update WorkoutListView!!!
}
func showExercises(forWorkout workout: Workout) {
WKInterfaceController.reloadRootControllers(withNamesAndContexts: [(name: "ExerciseListInterfaceController", context: workout as AnyObject)])
}
func showAddWorkout() {
presentController(withNamesAndContexts: [("AddWorkoutHostingController", context: "" as AnyObject)])
}
}
SwiftUI View:
struct WorkoutListView : View {
weak var host: WorkoutListInterfaceController?
#State private var shouldPresentAddWorkout = false
#State var workouts: [Workout] = Workout.all()
init(host: WorkoutListInterfaceController) {
self.host = host
}
var body: some View {
return List {
Section(header: Text("Workouts")
.font(.system(size: 18))
.fontWeight(.bold)
.frame(height: 18))
{
ForEach(workouts) { workout in
Button(action: {
self.host?.showExercises(forWorkout: workout)
}) {
Text(workout.title)
}
}
}
Button(action: {
self.host?.showAddWorkout()
}) {
Text("Add Workout")
.fontWeight(.medium)
.foregroundColor(.green)
.frame(minWidth: 0, maxWidth: .infinity, alignment: .center)
}
}
.sheet(isPresented: $shouldPresentAddWorkout, onDismiss: {}) {
AddWorkoutView(host: AddWorkoutHostingController())
}
.contextMenu {
Button(action: { self.host?.showAddWorkout() },
label: {
VStack {
Image(systemName: "plus")
Text("Add Workout")
}
})
}
}
}
WorkoutListInterfaceController's didAppear() gets called and I want to update WorkoutListView.
On onAppear(), swiftUI view modifier doesn't get called. I don't know if this is a bug or expected behavior, that's why I am defaulting to the didAppear() on the hosting controller to refresh the view.
Got this to work with using Combine and NotificationCenter by posting a notification when the presented view will dismiss:
NotificationCenter.default.post(name: .updateWorkoutsNotification, object: workouts)
And then subscribing to that notification in a WorkoutListView view model
Have you tried:
. setNeedsBodyUpdate()
https://developer.apple.com/documentation/swiftui/wkhostingcontroller/setneedsbodyupdate()