configuration.isOn.toggle() Not working in Custom Toggle in SwiftUI - ios

I have made a screen where I used a custom toggle. But when I am tapping on the toggle button, it's always printing "isOff" because the configuration.toggle value is not updating and is always set to false.
struct TestToggleView: View {
#Binding private var isOn: Bool
init(isOn: Binding<Bool>) {
_isOn = isOn
}
public var body: some View {
Toggle(isOn: $isOn, label: {
Text("Hi)
})
.toggleStyle(ToggleStyle())
}
}
private struct ButtonToggleStyle: ToggleStyle {
func makeBody(configuration: Configuration) -> some View {
HStack(alignment: .center) {
configuration.label
Text("Yes")
}.frame(maxWidth: .infinity)
.onTapGesture {
configuration.isOn.toggle()
if configuration.isOn {
print("isOn")
} else {
print("isOff")
}
}
.onAppear {
print(configuration.isOn)
}
}
}
}

Well, you should not name your custom ToggleStyle ToggleStyle.
To change the isOn value you have to go to the wrappedValue:
configuration.$isOn.wrappedValue.toggle()
Here is a complete example:
struct ContentView: View {
#State private var isOn = false
var body: some View {
Toggle(isOn: $isOn, label: {
Text("Hi") })
.toggleStyle(MyToggleStyle())
}
}
struct MyToggleStyle: ToggleStyle {
func makeBody(configuration: Configuration) -> some View {
HStack(alignment: .center) {
configuration.label
Text(configuration.isOn ? "Yes" : "No")
}.frame(maxWidth: .infinity)
.onTapGesture {
configuration.$isOn.wrappedValue.toggle()
}
}
}

Related

How to pass single Buttons into custom ConfirmationDialog

I'm working on a custom ConfirmationDialog with icons, which works fine. The downside is that in the view modifier I can only access all buttons as one content container, so I can't put a divider between them, and also can't disable the overlay on button action (I have to do this in the calling closure).
Any ideas how I can pass the single buttons with their actions down into the modifier?
I was playing around with multiple TupleView inits but I'm not sure how to apply them to the modifier and View extension, let alone the button action.
struct ContentView: View {
#State private var showConfirmationDialog = false
#State private var showModifierDialog = false
var body: some View {
VStack {
Button("Show Dialog") { showConfirmationDialog = true }
Button("Show ViewMod Dialog") {
withAnimation {
showModifierDialog = true
}
}
.padding()
}
.padding()
// standard confirmationDialog
.confirmationDialog("Test", isPresented: $showConfirmationDialog) {
Button { } label: {
Label("Add completion", systemImage: "checkmark.circle")
}
Button { } label: {
Label("Add Note", systemImage: "note.text.badge.plus")
}
Button("Cancel", role: .cancel) {}
}
// custom confirmationDialog with Icons, Cancel added automatically
.customConfirmDialog(isPresented: $showModifierDialog) {
Button {
// action
showModifierDialog = false
} label: {
Label("Add completion", systemImage: "checkmark.circle")
}
Divider() // unfortunately this is still necessary
Button {
// action
showModifierDialog = false
} label: {
Label("Add Note", systemImage: "note.text.badge.plus")
}
}
}
}
// *** Custom ConfirmDialog Modifier and View extension
extension View {
func customConfirmDialog<A: View>(isPresented: Binding<Bool>, #ViewBuilder actions: #escaping () -> A) -> some View {
return self.modifier(MyCustomModifier(isPresented: isPresented, actions: actions))
}
}
struct MyCustomModifier<A>: ViewModifier where A: View {
#Binding var isPresented: Bool
#ViewBuilder let actions: () -> A
func body(content: Content) -> some View {
ZStack {
content
.frame(maxWidth: .infinity, maxHeight: .infinity)
ZStack(alignment: .bottom) {
if isPresented {
Color.primary.opacity(0.2)
.ignoresSafeArea()
.onTapGesture {
isPresented = false
}
.transition(.opacity)
}
if isPresented {
VStack {
GroupBox {
actions()
.frame(maxWidth: .infinity, alignment: .leading)
}
GroupBox {
Button("Cancel", role: .cancel) {
isPresented = false
}
.bold()
.frame(maxWidth: .infinity, alignment: .center)
}
}
.font(.title3)
.padding(8)
.transition(.move(edge: .bottom))
}
}
}
.animation(.easeInOut, value: isPresented)
}
}
Here is first of many tuples (you should support all of them to be compatible with ViewBuilder)
Tested with Xcode 14b3 / iOS 16
extension View {
func customConfirmDialog<A: View, B: View>(isPresented: Binding<Bool>, #ViewBuilder actions: #escaping () -> TupleView<(A, B)>) -> some View {
return self.modifier(MyCustomModifier(isPresented: isPresented, actions: {
let buttons = actions()
VStack(alignment: .leading) {
buttons.value.0
Divider()
buttons.value.1
}
}))
}
}

How can I go to a view by clicking on a Button in SwiftUI

If the user clicks on connexionBtnView I want to redirect them to an AdminView or UserView
import SwiftUI
struct ConnexionView: View {
#State var loginId: String = ""
#State var pwd: String = ""
#StateObject private var keyboardHander = KeyBoardHandler()
var body: some View {
NavigationView{
ZStack{
Image("background")
.ignoresSafeArea()
VStack (spacing: 15){
Spacer()
logoView
Spacer()
titleView
loginIdView
loginPwdView
connexionBtnView
Spacer()
NavigationLink {
LostPwdView()
} label: {
lostPwd
}
Spacer()
}.frame(maxHeight: .infinity)
.padding(.bottom,keyboardHander.keyboardHeight)
.animation(.default)
}
}
}
The NavigationLink has the isActive parameter. You can pass it in the init of NavigationLink and when this state variable has the true value you will redirect to another view. Details here.
struct ConnexionView: View {
#State var isActive: Bool = false
var body: some View {
NavigationView {
VStack {
NavigationLink(isActive: $isActive) {
LostPwdView()
} label: {
Text("Some Label")
}
Button("Tap me!") {
isActive = true
}
}
}
}
}
struct LostPwdView: View {
var body: some View {
Text("Hello")
}
}
What you need to do is having a #State variable that would trigger the navigation:
.fullScreenCover(
isPresented: $viewShown
) {
print("View dismissed")
} content: {
NextView()
}
Where NextView() is the View you want to show and the viewShown is your State variable, below a full example:
struct ExampleView: View {
#State var isNextPageOpen = false
var body: some View {
Button("Tap Here To Navigate") {
isNextPageOpen = true
}
.fullScreenCover(
isPresented: $isNextPageOpen
) {
print("View dismissed")
} content: {
NextView()
}
}
}

SwiftUI view over all the views including sheet view

I need to show a view above all views based upon certain conditions, no matter what the top view is. I am trying the following code:
struct TestView<Presenting>: View where Presenting: View {
/// The binding that decides the appropriate drawing in the body.
#Binding var isShowing: Bool
/// The view that will be "presenting" this notification
let presenting: () -> Presenting
var body: some View {
GeometryReader { geometry in
ZStack(alignment: .top) {
self.presenting()
HStack(alignment: .center) {
Text("Test")
}
.frame(width: geometry.size.width - 44,
height: 58)
.background(Color.gray.opacity(0.7))
.foregroundColor(Color.white)
.cornerRadius(20)
.transition(.slide)
.opacity(self.isShowing ? 1 : 0)
}
}
}
}
extension View {
func showTopView(isShowing: Binding<Bool>) -> some View {
TestView(isShowing: isShowing,
presenting: { self })
}
}
struct ContentView: View {
#State var showTopView = false
NavigationView {
ZStack {
content
}
}
.showTopView(isShowing: $showTopView)
}
Now this is working fine in case of the views being pushed. But I am not able to show this TopView above the presented view.
Any help is appreciated!
Here is a way for your goal, you do not need Binding, just use let value.
struct ContentView: View {
#State private var isShowing: Bool = Bool()
var body: some View {
CustomView(isShowing: isShowing, content: { yourContent }, isShowingContent: { isShowingContent })
}
var yourContent: some View {
NavigationView {
VStack(spacing: 20.0) {
Text("Hello, World!")
Button("Show isShowing Content") { isShowing = true }
}
.navigationTitle("My App")
}
}
var isShowingContent: some View {
ZStack {
Color.black.opacity(0.5).ignoresSafeArea()
VStack {
Spacer()
Button("Close isShowing Content") { isShowing = false }
.foregroundColor(.white)
.padding()
.frame(maxWidth: .infinity)
.background(Color.blue.cornerRadius(10.0))
.padding()
}
}
}
}
struct CustomView<Content: View, IsShowingContent: View>: View {
let isShowing: Bool
#ViewBuilder let content: () -> Content
#ViewBuilder let isShowingContent: () -> IsShowingContent
var body: some View {
Group {
if isShowing { ZStack { content().blur(radius: isShowing ? 5.0 : 0.0); isShowingContent() } }
else { content() }
}
.animation(.default, value: isShowing)
}
}

How can I hide TabBar Swift UI?

I have a TabBarView in the root view. In one of the parent views that's nested within the root view, I'd like the tab bar to hide when navigating from that parent view to the child view. Is there any func or command to handle that?
Something like this:
ContentView (with TabBarView) - > ExploreView (Called in TabBarView ) -> MessagesView (Child of ExploreVIew - Hide Tab bar)
My code can be seen below.
TabView{
NavigationView{
GeometryReader { geometry in
ExploreView()
.navigationBarTitle(Text(""), displayMode: .inline)
.navigationBarItems(leading: Button(action: {
}, label: {
HStack{
Image("cityOption")
Text("BER")
Image("arrowCities")
}.foregroundColor(Color("blackAndWhite"))
.font(.system(size: 12, weight: .semibold))
}),trailing:
HStack{
Image("closttop")
.renderingMode(.template)
.padding(.trailing, 125)
NavigationLink(destination: MessagesView()
.navigationBarTitle(Text("Messages"), displayMode: .inline)
.navigationBarItems(trailing: Image("writemessage"))
.foregroundColor(Color("blackAndWhite"))
){
Image("messages")
}
}.foregroundColor(Color("blackAndWhite"))
)
}
}
.tabItem{
HStack{
Image("clostNav").renderingMode(.template)
Text("Explore")
.font(.system(size: 16, weight: .semibold))
}.foregroundColor(Color("blackAndWhite"))
}
SearchView().tabItem{
Image("search").renderingMode(.template)
Text("Search")
}
PostView().tabItem{
HStack{
Image("post").renderingMode(.template)
.resizable().frame(width: 35, height: 35)
}
}
OrdersView().tabItem{
Image("orders").renderingMode(.template)
Text("Orders")
}
ProfileView().tabItem{
Image("profile").renderingMode(.template)
Text("Profile")
}
}
Appreciate any help! Thanks!
Create CustumPresentViewController.swift -
import UIKit
import SwiftUI
struct ViewControllerHolder {
weak var value: UIViewController?
}
struct ViewControllerKey: EnvironmentKey {
static var defaultValue: ViewControllerHolder { return
ViewControllerHolder(value:
UIApplication.shared.windows.first?.rootViewController ) }
}
extension EnvironmentValues {
var viewController: ViewControllerHolder {
get { return self[ViewControllerKey.self] }
set { self[ViewControllerKey.self] = newValue }
}
}
extension UIViewController {
func present<Content: View>(style: UIModalPresentationStyle =
.automatic, #ViewBuilder builder: () -> Content) {
// Must instantiate HostingController with some sort of view...
let toPresent = UIHostingController(rootView:
AnyView(EmptyView()))
toPresent.modalPresentationStyle = style
// ... but then we can reset rootView to include the environment
toPresent.rootView = AnyView(
builder()
.environment(\.viewController, ViewControllerHolder(value:
toPresent))
)
self.present(toPresent, animated: true, completion: nil)
}
}
Use this in required View -
#Environment(\.viewController) private var viewControllerHolder:
ViewControllerHolder
private var viewController: UIViewController? {
self.viewControllerHolder.value
}
var body: some View {
NavigationView {
ZStack {
Text("Navigate")
}.onTapGesture {
self.viewController?.present(style: .fullScreen) {
EditUserView()
}
}
}
}
A bit late here, on iOS 16 you could use ContentView().toolbar(
.hidden, for: .tabBar)
So in your case, it would be like as below,
struct ExploreView: View {
var body: some View {
some_view {
}
.toolbar(.hidden, for: .tabBar)
}
}
in a normal iphone environment the tabbar vanishes from itself if you are navigating....or are u running in another environment?
check this:
struct ContentView: View {
#State var hideTabbar = false
#State var navigate = false
var body: some View {
NavigationView {
TabView {
VStack {
NavigationLink(destination: Text("without tab")){
Text("aha")
}
.tabItem {
Text("b")
}
}.tag(0)
Text("Second View")
.tabItem {
GeometryReader { geometry in
Image(systemName: "2.circle")
// Text("Second")
Text("\(geometry.size.height) - \(geometry.size.width)")
}
}.tag(1)
}
}
}//.isHidden(self.hideTabbar)
}

Present a new view in SwiftUI

I want to click a button and then present a new view like present modally in UIKit
I have already seen "How to present a new view using sheets", but I don't want to attach it to the main view as a modal sheet.
And I don't want to use NavigationLink, because I don't want a new view and old view have a navigation relationship.
Thanks for your help...
To show a modal (iOS 13 style)
You just need a simple sheet with the ability to dismiss itself:
struct ModalView: View {
#Binding var presentedAsModal: Bool
var body: some View {
Button("dismiss") { self.presentedAsModal = false }
}
}
And present it like:
struct ContentView: View {
#State var presentingModal = false
var body: some View {
Button("Present") { self.presentingModal = true }
.sheet(isPresented: $presentingModal) { ModalView(presentedAsModal: self.$presentingModal) }
}
}
Note that I passed the presentingModal to the modal so you can dismiss it from the modal itself, but you can get rid of it.
To make it REALLY present fullscreen (Not just visually)
You need to access to the ViewController. So you need some helper containers and environment stuff:
struct ViewControllerHolder {
weak var value: UIViewController?
}
struct ViewControllerKey: EnvironmentKey {
static var defaultValue: ViewControllerHolder {
return ViewControllerHolder(value: UIApplication.shared.windows.first?.rootViewController)
}
}
extension EnvironmentValues {
var viewController: UIViewController? {
get { return self[ViewControllerKey.self].value }
set { self[ViewControllerKey.self].value = newValue }
}
}
Then you should use implement this extension:
extension UIViewController {
func present<Content: View>(style: UIModalPresentationStyle = .automatic, #ViewBuilder builder: () -> Content) {
let toPresent = UIHostingController(rootView: AnyView(EmptyView()))
toPresent.modalPresentationStyle = style
toPresent.rootView = AnyView(
builder()
.environment(\.viewController, toPresent)
)
NotificationCenter.default.addObserver(forName: Notification.Name(rawValue: "dismissModal"), object: nil, queue: nil) { [weak toPresent] _ in
toPresent?.dismiss(animated: true, completion: nil)
}
self.present(toPresent, animated: true, completion: nil)
}
}
Finally
you can make it fullscreen like:
struct ContentView: View {
#Environment(\.viewController) private var viewControllerHolder: UIViewController?
var body: some View {
Button("Login") {
self.viewControllerHolder?.present(style: .fullScreen) {
Text("Main") // Or any other view you like
// uncomment and add the below button for dismissing the modal
// Button("Cancel") {
// NotificationCenter.default.post(name: Notification.Name(rawValue: "dismissModal"), object: nil)
// }
}
}
}
}
For iOS 14 and Xcode 12:
struct ContentView: View {
#State private var isPresented = false
var body: some View {
Button("Show Modal with full screen") {
self.isPresented.toggle()
}
.fullScreenCover(isPresented: $isPresented, content: FullScreenModalView.init)
}
}
struct FullScreenModalView: View {
#Environment(\.presentationMode) var presentationMode
var body: some View {
VStack {
Text("This is a modal view")
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.red)
.edgesIgnoringSafeArea(.all)
.onTapGesture {
presentationMode.wrappedValue.dismiss()
}
}
}
See also: How to present a full screen modal view using fullScreenCover()
Disclaimer: Below is not really like a "native modal", neither behave nor look&feel, but if anyone would need a custom transition of one view over other, making active only top one, the following approach might be helpful.
So, if you expect something like the following
Here is a simple code for demo the approach (of corse animation & transition parameters can be changed by wish)
struct ModalView : View {
#Binding var activeModal: Bool
var body : some View {
VStack {
Button(action: {
withAnimation(.easeInOut(duration: 0.3)) {
self.activeModal = false
}
}) {
Text("Hide modal")
}
Text("Modal View")
}
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .center)
.background(Color.green)
}
}
struct MainView : View {
#Binding var activeModal: Bool
var body : some View {
VStack {
Button(action: {
withAnimation(.easeInOut(duration: 0.3)) {
self.activeModal = true
}
}) {
Text("Show modal")
}
Text("Main View")
}
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .center)
.background(Color.yellow)
}
}
struct ModalContainer: View {
#State var showingModal = false
var body: some View {
ZStack {
MainView(activeModal: $showingModal)
.allowsHitTesting(!showingModal)
.disabled(showingModal)
if showingModal {
ModalView(activeModal: $showingModal)
.transition(.move(edge: .bottom))
.zIndex(1)
}
}
}
}
Here is a simple one way - forward views. It's very straight forward.
struct ChildView: View{
private let colors: [Color] = [.red, .yellow,.green,.white]
#Binding var index : Int
var body: some View {
let next = (self.index+1) % MyContainer.totalChildren
return ZStack{
colors[self.index % colors.count]
Button("myNextView \(next) ", action: {
withAnimation{
self.index = next
}
}
)}.transition(.asymmetric(insertion: .move(edge: .trailing) , removal: .move(edge: .leading) ))
}
}
struct MyContainer: View {
static var totalChildren = 10
#State private var value: Int = 0
var body: some View {
HStack{
ForEach(0..<(Self.totalChildren) ) { index in
Group{
if index == self.value {
ChildView(index: self.$value)
}}
}
}
}
}
then presents it from ContentView when a button is tapped:
struct SheetView: View {
#Environment(\.dismiss) var dismiss
var body: some View {
Button("Press to dismiss") {
dismiss()
}
.font(.title)
.padding()
.background(Color.black)
}
}
struct ContentView: View {
#State private var showingSheet = false
var body: some View {
Button("Show Sheet") {
showingSheet.toggle()
}
.sheet(isPresented: $showingSheet) {
SheetView()
}
}
}

Resources