How do you create a SwiftUI view that takes an optional secondary View argument?
November 2021 update (Works in Xcode 11.x, 12.x, and 13.x)
After some thought and a bit of trial and error, I figured it out. It seems a bit obvious in hindsight.
struct SomeCustomView<Content>: View where Content: View {
let title: String
let content: Content
init(title: String, @ViewBuilder content: @escaping () -> Content) {
self.title = title
self.content = content()
}
// returns a new View that includes the View defined in 'body'
func sideContent<SideContent: View>(@ViewBuilder side: @escaping () -> SideContent) -> some View {
HStack {
self // self is SomeCustomView
side()
}
}
var body: some View {
VStack {
Text(title)
content
}
}
}
It works with or without the method call.
SomeCustomView(title: "string argument") {
// some view
}
SomeCustomView(title: "hello") {
// some view
}.sideContent {
// another view
}
Previous code with subtle bug: body
should be self
func sideContent<SideContent: View>(@ViewBuilder side: @escaping () -> SideContent) -> some View {
HStack {
body // <--- subtle bug, updates to the main View are not propagated
side()
}
}
Thank you Jordan Smith for pointing this out a long time ago.
How to apply multiple modifier in a function to a given View in SwiftUI?
I would solve this task in reverse order - generic implementation for array and single use as array with one element, like:
extension SwiftUI.View {
public func border(edge: Edge, color: SwiftUI.Color, width: CGFloat = 1, cornerRadius: CGFloat = 0) -> some SwiftUI.View {
self.border(edges: [edge], color: color, width: width, cornerRadius: cornerRadius)
}
public func border(edges: [Edge], color: SwiftUI.Color, width: CGFloat = 1, cornerRadius: CGFloat = 0) -> some View {
self // generic implementation here
}
}
SwiftUI: Array Not Updating In All Views When Referencing The Same Observed Object
Ok I got it working. All I had to do was move the functions I had in my view to my viewModel. Im guessing to keep everything in the same scope. My guess is that everything in ContentView was it's own copy. I'm not 100% certain on that, but it works now.
SwiftUI: Global Overlay That Can Be Triggered From Any View
It's quite easy - and entertaining - to build a "toast" in SwiftUI!
Let's do it!
struct Toast<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 toast
let presenting: () -> Presenting
/// The text to show
let text: Text
var body: some View {
GeometryReader { geometry in
ZStack(alignment: .center) {
self.presenting()
.blur(radius: self.isShowing ? 1 : 0)
VStack {
self.text
}
.frame(width: geometry.size.width / 2,
height: geometry.size.height / 5)
.background(Color.secondary.colorInvert())
.foregroundColor(Color.primary)
.cornerRadius(20)
.transition(.slide)
.opacity(self.isShowing ? 1 : 0)
}
}
}
}
Explanation of the body:
GeometryReader
gives us the preferred size of the superview , thus allowing the perfect sizing for ourToast
.ZStack
stacks views on top of each other.- The logic is trivial: if the toast is not supposed to be seen (
isShowing == false
), then we render thepresenting
view. If the toast has to be presented (isShowing == true
), then we render thepresenting
view with a little bit of blur - because we can - and we create our toast next. - The toast is just a
VStack
with aText
, with custom frame sizing, some design bells and whistles (colors and corner radius), and a defaultslide
transition.
I added this method on View
to make the Toast
creation easier:
extension View {
func toast(isShowing: Binding<Bool>, text: Text) -> some View {
Toast(isShowing: isShowing,
presenting: { self },
text: text)
}
}
And a little demo on how to use it:
struct ContentView: View {
@State var showToast: Bool = false
var body: some View {
NavigationView {
List(0..<100) { item in
Text("\(item)")
}
.navigationBarTitle(Text("A List"), displayMode: .large)
.navigationBarItems(trailing: Button(action: {
withAnimation {
self.showToast.toggle()
}
}){
Text("Toggle toast")
})
}
.toast(isShowing: $showToast, text: Text("Hello toast!"))
}
}
I used a NavigationView
to make sure the view fills the entire screen, so the Toast
is sized and positioned correctly.
The withAnimation
block ensures the Toast
transition is applied.
How it looks:
It's easy to extend the Toast
with the power of SwiftUI DSL.
The Text
property can easily become a @ViewBuilder
closure to accomodate the most extravagant of the layouts.
To add it to your content view:
struct ContentView : View {
@State private var liked: Bool = false
var body: some View {
VStack {
LikeButton(liked: $liked)
}
// make it bigger by using "frame" or wrapping it in "NavigationView"
.toast(isShowing: $liked, text: Text("Hello toast!"))
}
}
How to hide the toast afte 2 seconds (as requested):
Append this code after .transition(.slide)
in the toast VStack
.
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
withAnimation {
self.isShowing = false
}
}
}
Tested on Xcode 11.1
How can gave a View as default value in a View Function in SwiftUI?
The most possible way is having 2 function's. Like down codes:
PS: We cannot have the function which carries the default value for a generic type.
struct ContentView: View {
var body: some View {
viewFunction(content: Text("Hello, world!"))
viewFunction()
}
}
func viewFunction<Content: View>(content: Content) -> some View {
return content
}
func viewFunction() -> some View {
return Text("Hello, world!")
}
Conditionally use view in SwiftUI
The simplest way to avoid using an extra container like HStack
is to annotate your body
property as @ViewBuilder
, like this:
@ViewBuilder
var body: some View {
if user.isLoggedIn {
MainView()
} else {
LoginView()
}
}
Related Topics
Using Uiapplicationdelegateadaptor to Get Callbacks from Userdidacceptcloudkitsharewith Not Working
Alamofire Type 'Parameterencoding' Has No Member 'Url' Swift 3
How to Prevent Timer Slowing Down in Background
Flip Arfaceanchor from Left-Handed to Right-Handed Coordinate System
Nsdata Contentsofurl Constructor Returns Nil
Collectionview Not Display Data After Parsing JSON
Save & Retrieve Tableviewcell Checkmark Using Nsuserdefaults in Swift
Swift: Double Conversion Inconsistency. How to Correctly Compare Doubles
Swiftui: How to Make Entire Shape Recognize Gestures When Stroked
Animating a Navigation Bar Color
Gcd with Static Functions of a Struct
How to Stream Remote Audio in iOS 13? (Swiftui)
Multi-Face Detection in Realitykit
How to Set Font Size of Sklabelnode to Fit in Fixed Size (Swift)