I'm building my first SwiftUI app and I've run into a blocker. When a user long-presses one of my cells, I want to show a confirmationdialog with custom buttons.
Here's the code:
.confirmationDialog("", isPresented: $showLongPressMenu) {
Button {
//
} label: {
HStack {
Image(systemName: "checkmark.circle")
Text("Add completion")
}
}
Button {
//
} label: {
HStack {
Image(systemName: "note.text.badge.plus")
Text("Add Note")
}
}
Button("Cancel", role: .cancel) {}
}
This is sort-of working, here's the result:
But what I'm trying to achieve is something like this:
Any pointers would be amazing, thank you.
Here is an approach I'm working on:
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's a quick pure SwiftUI custom Dialog I wrote using resultBuilder avoiding the Divider approach:
Package URL: https://github.com/NoeOnJupiter/ComplexDialog/
Usage:
struct ContentView: View {
#State var isPresented = false
#State var color = Color.green
var body: some View {
Button(action: {
isPresented.toggle()
}) {
Text("This is a Test")
.foregroundColor(.white)
}.padding(20)
.background(color)
.clipShape(RoundedRectangle(cornerRadius: 12, style: .continuous))
.presentDialog(isPresented: $isPresented, bodyContent: {
HStack {
Image(systemName: "circle.fill")
.foregroundColor(.red)
Text("Red Color")
Spacer()
}.dialogAction {
color = .red
}
HStack {
Image(systemName: "circle.fill")
.foregroundColor(.blue)
Text("Blue Color")
Spacer()
}.dialogAction {
color = .blue
}
HStack {
Image(systemName: "circle.fill")
.foregroundColor(.green)
Text("Green Color")
Spacer()
}.dialogAction {
color = .green
}
}, cancelContent: {
HStack {
Spacer()
Text("Cancel")
.font(.title3)
.fontWeight(.semibold)
Spacer()
}.dialogAction {
}
})
}
}
Related
I'm trying to implement a chat feature, the relevant codes are simple enough
struct ContentView: View {
#State private var channelId = ""
#State private var message = ""
#ObservedObject var sig = SignalRService(urlStr: "\(API.HubsEndpoint)/hubs/theHub")
var body: some View {
VStack {
HStack {
TextField("Channel ID", text: $channelId) .textFieldStyle(.roundedBorder)
Button {
sig.invoke(method: "JoinRoom", arguments: [channelId]) { error in
print("joined")
if let err = error {
print(err)
}
}
} label: {
Text("Join")
}
.padding(5)
.background(Color.accentColor)
.foregroundColor(.white)
.cornerRadius(2)
}
.padding()
Spacer()
ScrollView {
ScrollViewReader { scrollView in
LazyVStack(alignment: .leading, spacing: 5) {
ForEach(sig.messages) { msg in
HStack(alignment: .top) {
Text(msg.user)
.font(.caption)
.bold()
Text(msg.message)
.font(.caption)
}
.id(msg.id)
.padding(20)
.background(.regularMaterial)
.foregroundColor(.white)
.cornerRadius(10)
}
}
.onChange(of: sig.messages.last) { newValue in
scrollView.scrollTo(newValue?.id)
}
}
.frame(height: UIScreen.main.bounds.height/4, alignment:.bottom)
}
.frame(height: UIScreen.main.bounds.height/4, alignment:.bottom)
.background(Color.black)
HStack {
TextField("Message", text: $message)
.textFieldStyle(.roundedBorder)
Button {
sig.invoke(method: "SendMessage", arguments: [message, channelId]) { error in
print("messaged")
if let err = error {
print(err)
}
else {
message = ""
}
}
} label: {
Image(systemName: "paperplane.fill")
}
.padding(5)
.background(Color.accentColor)
.foregroundColor(.white)
.cornerRadius(2)
}
.padding(.horizontal)
}
}
}
And the result: https://imgur.com/a/9JdPkib
It kept snapping to the bottom when I tried to scroll up.
I'm clipping the frame size and increased the padding to exaggerate the views, so it's easier to test.
Any idea what's causing the issue?
I want once I press the button search
VStack{
Text("Enter friends first name")
.font(.caption)
.fontWeight(.bold)
.foregroundColor(Color("Color"))
TextField("firstname", text: $firstname)
.padding()
.keyboardType(.default)
.background(Color.white)
.autocapitalization(.none)
.textFieldStyle(.roundedBorder)
.shadow(color: Color.gray.opacity(0.1), radius: 5, x: 0, y: 2)
Text("Enter friends last Name")
.font(.caption)
.fontWeight(.bold)
.foregroundColor(Color("Color"))
TextField("lastname", text: $lastname)
.padding()
.keyboardType(.default)
.background(Color.white)
.autocapitalization(.none)
.textFieldStyle(.roundedBorder)
.shadow(color: Color.gray.opacity(0.1), radius: 5, x: 0, y: 2)
Button (action:{
searchUser()
},label:{
Text("Search")
})
}
the list that is in searchUser()that shows the names of friends with this first name and last name and their details appears on the this view under the search button and once the button is pressed but with animation ? thanks
I tried to do the animation but it didn't work. does anyone know how can I do it ?
You can show/hide views conditionally by putting them inside if block.
struct ContentView: View {
#State var shouldShowList = false
var body: some View {
VStack {
if shouldShowList {
VStack {
ForEach(0 ..< 5) { item in
Text("Hello, world!")
.padding()
}
}
}
Button( shouldShowList ? "Hide" : "Show") {
shouldShowList.toggle()
}
}
.animation(.easeInOut, value: shouldShowList) // animation
}
}
Instead,
You can use a view modifier to show/hide.
1. create your own ViewModifire
struct Show: ViewModifier {
let isVisible: Bool
#ViewBuilder
func body(content: Content) -> some View {
if isVisible {
EmptyView()
} else {
content
}
}
}
extension View {
func show(isVisible: Bool) -> some View {
ModifiedContent(content: self, modifier: Show(isVisible: isVisible))
}
}
Usage
struct ContentView: View {
#State var shouldShowList = false
var body: some View {
VStack {
VStack {
ForEach(0 ..< 5) { item in
Text("Hello, world!")
.padding()
}
}
.show(isVisible: shouldShowList) //<= here
Button( shouldShowList ? "Hide" : "Show") {
shouldShowList.toggle()
}
}
.animation(.easeInOut, value: shouldShowList) // animation
}
}
I'm trying to have multiple expandable views with animation inside a VStack. I have the following code:
struct ContentView: View {
var body: some View {
NavigationView {
ScrollView {
VStack {
ExpandableView(headerTitle: "First")
ExpandableView(headerTitle: "Second")
Spacer()
}
}
}
}
}
And the ExpandableView:
struct ExpandableView: View {
let headerTitle: String
#State private var collapsed: Bool = true
var body: some View {
Button(
action: {
self.collapsed.toggle()
},
label: {
VStack(spacing: 2) {
ZStack {
Rectangle()
.fill(.gray)
VStack {
Text("\(headerTitle) Header")
if !collapsed {
HStack {
Text("Text A")
Text("Text B")
}
}
}
}
.frame(height: collapsed ? 52 : 80)
ZStack(alignment: .top) {
Rectangle()
.fill(.gray)
.frame(height: 204)
VStack {
Text("Content A")
Text("Content B")
Text("Content C")
}
}
.frame(maxHeight: collapsed ? 0 : .none)
.clipped()
}
}
)
.buttonStyle(PlainButtonStyle())
.animation(.easeOut, value: collapsed)
}
}
The result is this:
As you can see if I open the last expandableView is opens correctly. However if I open the first one when the second is closed, it actually opens the second. It only opens correctly the first one if the second is already open. It seems the VStack is not rendering correctly itself. Any ideas why this happening?
Thanks for the help.
I migth be the way the buttons works. Here is a cleaner solution:
struct ExpandableView: View {
let headerTitle: String
#State private var collapsed: Bool = true
var body: some View {
VStack(spacing: 2) {
Button(
action: {
withAnimation(.easeOut){
self.collapsed.toggle()
}
},
label: {
VStack {
Text("\(headerTitle) Header")
if !collapsed {
HStack {
Text("Text A")
Text("Text B")
}
}
}.frame(maxWidth: .infinity)
})
.buttonStyle(.borderedProminent)
.tint(.gray)
if(!self.collapsed) {
VStack {
Divider().background(.black)
Text("Content A")
Text("Content B")
Text("Content C")
}
}
Spacer()
}
.frame(height: collapsed ? 52 : 204)
.frame(maxWidth: .infinity)
.background(.gray)
.padding()
}
}
How can I add a badge to navigationBarItems in SwiftUI and iOS 14?
I cannot find anything on the net...
I want for example add a badge over the leading navigationBarItems:
var body: some View {
NavigationView {
ZStack {
VStack(spacing: 0) {
Text("Peanut")
.padding(-10)
.navigationBarTitle(Text("HomeTitle"), displayMode: .inline)
.navigationBarItems(leading:
HStack {
NavigationLink(destination: Notifications()) {
Image(systemName: "bell")
.font(.system(size: 20))
}.foregroundColor(.white)
}, trailing:
HStack {
NavigationLink(destination: Settings()) {
Image(systemName: "gearshape")
.font(.system(size: 20))
}.foregroundColor(.white)
})
}
}
}
}
You can create a custom Badge view:
struct Badge: View {
let count: Int
var body: some View {
ZStack(alignment: .topTrailing) {
Color.clear
Text(String(count))
.font(.system(size: 16))
.padding(5)
.background(Color.red)
.clipShape(Circle())
// custom positioning in the top-right corner
.alignmentGuide(.top) { $0[.bottom] }
.alignmentGuide(.trailing) { $0[.trailing] - $0.width * 0.25 }
}
}
}
and use it as an overlay:
struct ContentView: View {
var body: some View {
NavigationView {
ZStack {
VStack(spacing: 0) {
Text("Peanut")
.padding(-10)
.navigationBarTitle(Text("HomeTitle"), displayMode: .inline)
.navigationBarItems(leading: leadingBarItems)
}
}
}
}
var leadingBarItems: some View {
NavigationLink(destination: Text("Notifications")) {
Image(systemName: "bell")
.font(.system(size: 20))
}
.foregroundColor(.primary)
.padding(5)
.overlay(Badge(count: 3))
}
}
Note
The badge view uses alignment guides for positioning. For more information see:
Alignment Guides in SwiftUI
Here's another example of custom badge
struct BadgeViewModifier: ViewModifier {
let text: String?
func body(content: Content) -> some View {
content
.overlay(alignment: .topTrailing) {
text.map { value in
Text(value)
.fixedSize(horizontal: true, vertical: false)
.font(.system(size: 14, weight: .semibold))
.foregroundColor(DS.Colors.white)
.padding(.horizontal, value.count == 1 ? 2 : 6)
.padding(.vertical, 2)
.background(
Capsule()
.fill(DS.Colors.red)
.if(value.count == 1) { $0.aspectRatio(1, contentMode: .fill) }
)
}
}
}
}
extension View {
func badge(value: String?) -> some View {
modifier(BadgeViewModifier(text: value))
}
#ViewBuilder func `if`<Result: View>(_ condition: Bool, closure: #escaping (Self) -> Result) -> some View {
if condition {
closure(self)
} else {
self
}
}
}
Sorry if this is a really stupid question and maybe offtopic, but I can't seem to find it anywhere.
I'm trying to do a simple settings section for my app and would like to add icons to my elements so users can easily understand what does each setting does. To achieve that, I used Horizontal Stacks (HStack) with an Image and a Label (Text).
This somehow does the trick but I'd like to know if there's a cleaner way to do this.
I'd also like to know how to adjust the separator between cells to stop on the label, and not continue until the end of the element.
As I'm not really good at explaining, here you have two images:
This is what I got
I'd like to have something similar to this
My code:
SettingsView.swift
struct SettingsView: View {
#State var age: Int = 0
var body: some View {
UITableView.appearance().separatorStyle = .singleLine
UINavigationBar.appearance().shadowImage = UIImage()
return NavigationView {
Form {
Section {
Picker(selection: .constant(1), label: HStack {
Image(systemName: "paintbrush")
.font(Font.system(size: 25, weight: .light))
Text("Editor Theme")
}) {
Text("Ayu Mirage").tag(1)
Text("Ayu Light").tag(2)
}
Stepper(value: self.$age,
in: 18...100,
label: {
HStack {
Image(systemName: "rectangle.stack")
.font(Font.system(size: 25, weight: .light))
Text("Number of snippets: \(self.age)")
}
})
NavigationLink(destination: NotificationSettingsView()) {
HStack {
Image(systemName: "app.badge")
.font(Font.system(size: 25, weight: .light))
Text("Notifications")
}
}
}
Section {
VStack(alignment: .leading, spacing: 5) {
Text("Mercury v1.0")
.font(.headline)
Text("Designed with ❤️ by amodrono")
.font(.footnote)
}.padding(.vertical, 5)
}
}
.navigationBarTitle("Hello")
.environment(\.horizontalSizeClass, .regular)
.navigationBarTitle(Text("Settings"), displayMode: .inline)
.navigationBarItems(leading: (
Button(action: {
}) {
Image(systemName: "xmark")
.font(.headline)
.imageScale(.large)
.foregroundColor(Color(UIColor(named: "adaptiveColor")!))
}
))
}
}
}
You can disable the separator from the tableview and add your own divider.
Something like this:
struct ContentView: View {
var body: some View {
UITableView.appearance().separatorStyle = .none
return NavigationView {
Form {
Section {
RowView(iconName:"rectangle.stack", text:"Some text")
RowView(iconName:"paintbrush", text:"Some other text", showDivider: false)
}
}
}
}
}
struct RowView: View {
var iconName: String
var text: String
var showDivider = true
var body: some View {
HStack(alignment: .firstTextBaseline) {
Image(systemName: iconName)
VStack(alignment: .leading) {
Text(text)
if showDivider {
Divider()
} else {
Spacer()
}
}
}
}
}
do you mean like this? (if yes, just switch darkmode on ;))
import SwiftUI
#available(iOS 13.0, *)
public struct DarkView<Content> : View where Content : View {
var darkContent: Content
var on: Bool
public init(_ on: Bool, #ViewBuilder content: () -> Content) {
self.darkContent = content()
self.on = on
}
public var body: some View {
ZStack {
if on {
Spacer()
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
.background(Color.black)
.edgesIgnoringSafeArea(.all)
darkContent.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity).background(Color.black).colorScheme(.dark)
} else {
darkContent
}
}
}
}
#available(iOS 13.0, *)
extension View {
#available(iOS 13.0, *)
public func darkModeFix(_ on: Bool = true) -> DarkView<Self> {
DarkView(on) {
self
}
}
}
struct ContentView: View {
#State var age: Int = 0
var body: some View {
UITableView.appearance().separatorStyle = .singleLine
UINavigationBar.appearance().shadowImage = UIImage()
return NavigationView {
Form {
Section {
Picker(selection: .constant(1), label: HStack {
Image(systemName: "paintbrush")
.font(Font.system(size: 25, weight: .light))
Text("Editor Theme")
}) {
Text("Ayu Mirage").tag(1)
Text("Ayu Light").tag(2)
}
Stepper(value: self.$age,
in: 18...100,
label: {
HStack {
Image(systemName: "rectangle.stack")
.font(Font.system(size: 25, weight: .light))
Text("Number of snippets: \(self.age)")
}
})
// NavigationLink(destination: NotificationSettingsView()) {
// HStack {
// Image(systemName: "app.badge")
// .font(Font.system(size: 25, weight: .light))
//
// Text("Notifications")
// }
// }
}
Section {
VStack(alignment: .leading, spacing: 5) {
Text("Mercury v1.0")
.font(.headline)
Text("Designed with ❤️ by amodrono")
.font(.footnote)
}.padding(.vertical, 5)
}
}
.navigationBarTitle("Hello")
.environment(\.horizontalSizeClass, .regular)
.navigationBarTitle(Text("Settings"), displayMode: .inline)
.navigationBarItems(leading: (
Button(action: {
}) {
Image(systemName: "xmark")
.font(.headline)
.imageScale(.large)
// .foregroundColor(Color(UIColor(named: "adaptiveColor")!))
}
))
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.environment(\.colorScheme, .dark)
.darkModeFix()
}
}