How can I use function type? - swift

I want to know if there is a way to use a function Type as var, for example in this code I am trying to send a function to get triggered in a Button tap Action. Is this kind of programming even possible in Swift?
The down code is not working and It is for SwiftUI, but the question is applicable to Swift as well.
struct ContentView: View {
#State var backgroundColor: Color = Color.white
func backgroundColorFunction() { backgroundColor = Color.red }
var body: some View {
ZStack {
backgroundColor.ignoresSafeArea()
CustomView(incomingFuction: backgroundColorFunction()) // :Here
}
}
}
struct CustomView: View {
var incomingFuction: ???funcType??? = ???funcType???()
var body: some View {
Button("update Background Color") {
incomingFuction()
}
}
}

Yes, it is possible, moreover it is widely used, especially in SwiftUI.
Here is your updated code:
struct FuncContentView: View {
#State var backgroundColor: Color = Color.white
func backgroundColorFunction() { backgroundColor = Color.red }
var body: some View {
ZStack {
backgroundColor.ignoresSafeArea()
CustomView(incomingFuction: backgroundColorFunction) // << here !!
}
}
}
struct CustomView: View {
var incomingFuction: () -> () // << here !!
var body: some View {
Button("update Background Color") {
incomingFuction()
}
}
}

Related

SwiftUI polymorphic behaviour not working for View

protocol BackgroundContent: View{
}
struct BlueDivider: BackgroundContent {
var body: some View {
Divider()
.frame(minHeight: 1)
.background(.blue)
}
}
struct RedDivider: BackgroundContent {
var body: some View {
Divider()
.frame(minHeight: 1)
.background(.red)
}
}
var p: BackgroundContent = BlueDivider()
// Use of protocol 'BackgroundContent' as a type must be written 'any BackgroundContent'
p = RedDivider()
This always ask me to use
var p: any BackgroundContent = BlueDivider()
Is there any way to use generic type which accept any kind view?
Actually, I want to use view as a state like #State private var bgView: BackgroundContent = BlueDivider() which i want to change at runtime like bgView = RedDivider()
I have made my custome view to place some other view at runtime by using this state.
For your specific problem you can do something like this here:
struct SwiftUIView: View {
#State var isRed = false
var body: some View {
Devider()
.frame(height: 1)
.background(isRed ? Color.red : Color.blue)
}
}
It is complicated but i have found a solution of this problem. First thing i have done with ObservableObject. Here is my example.
protocol BaseBackgroundContent {
var color: Color { get set }
}
class BlueContent: BaseBackgroundContent {
var color: Color = .blue
}
class RedContent: BaseBackgroundContent {
var color: Color = .red
}
And i created a custom view for Divider in this case.
struct CustomDivider: View {
var backgroundContent: any BaseBackgroundContent
var body: some View {
Divider()
.background(backgroundContent.color)
}
}
And now i used a viewModel which can be observable, and the protocol has to be Published.
class ExampleViewModel: ObservableObject {
#Published var backgroundContent: any BaseBackgroundContent = RedContent()
func change() {
backgroundContent = BlueContent()
}
}
Final step is the view. This is a exampleView. If you click the button you will see the BlueContent which was RedContent
struct Example: View {
#ObservedObject var viewModel = ExampleViewModel()
init() {
}
var body: some View {
VStack {
Text("Test")
CustomDivider(backgroundContent: viewModel.backgroundContent)
Button("Change") {
viewModel.change()
}
}
}
}

SwiftUI - stack multiple method calls in parent view

I am kind of a SwiftUI newbe but my question is essentially this:
I have a view that looks like this:
struct myView: View {
var label = Text("label")
var subLabel = Text("sublabel")
var body: some View {
VStack {
label
subLabel
}
}
public func primaryColor(color: Color) -> some View {
var view = self
view.label = view.label.foregroundColor(color)
return view.id(UUID())
}
public func secondaryColor(color: Color) -> some View {
var view = self
view.subLabel = view.subLabel.foregroundColor(color)
return view.id(UUID())
}
}
And here is my problem:
In my parent view, I would like to call myView as follows
struct parentView: View {
var body: some View {
myView()
.primaryColor(color: .red)
.secondaryColor(color: .blue)
}
}
Using only one of these modifiers works fine but stacking them won't work (since they return some View ?).
I don't think that I can use standard modifiers since I have to access myView variables, which (I think) wouldn't be possible by using a ViewModifier.
Is there any way to achieve my goal or am I going on the wrong direction ?
Here is a solution for you - remove .id (it is really not needed, result will be a copy anyway):
struct myView: View {
var label = Text("label")
var subLabel = Text("sublabel")
var body: some View {
VStack {
label
subLabel
}
}
public func primaryColor(color: Color) -> Self {
var view = self
view.label = view.label.foregroundColor(color)
return view
}
public func secondaryColor(color: Color) -> Self {
var view = self
view.subLabel = view.subLabel.foregroundColor(color)
return view
}
}
and no changes in parent view
Demo prepared with Xcode 13 / iOS 15
I'd suggest a refactor where primaryColor and secondaryColor can be passed as optional parameters to your MyView (in Swift, it is common practice to capitalize type names):
struct MyView: View {
var primaryColor : Color = .primary
var secondaryColor : Color = .secondary
var body: some View {
VStack {
Text("label")
.foregroundColor(primaryColor)
Text("sublabel")
.foregroundColor(secondaryColor)
}
}
}
struct ParentView: View {
var body: some View {
MyView(primaryColor: .red, secondaryColor: .blue)
}
}
This way, the code is much shorter and more simple, and follows the general form/practices of SwiftUI.
You could also use Environment keys/values to pass the properties down, but this takes more code (shown here for just the primary color, but you could expand it to secondary as well):
private struct PrimaryColorKey: EnvironmentKey {
static let defaultValue: Color = .primary
}
extension EnvironmentValues {
var primaryColor: Color {
get { self[PrimaryColorKey.self] }
set { self[PrimaryColorKey.self] = newValue }
}
}
extension View {
func primaryColor(_ primary: Color) -> some View {
environment(\.primaryColor, primary)
}
}
struct MyView: View {
#Environment(\.primaryColor) var primaryColor : Color
var body: some View {
VStack {
Text("label")
.foregroundColor(primaryColor)
}
}
}
struct ParentView: View {
var body: some View {
MyView()
.primaryColor(.red)
}
}

How to correctly handle Picker in Update Views (SwiftUI)

I'm quite new to SwiftUI and I'm wondering how I should use a picker in an update view correctly.
At the moment I have a form and load the data in with .onAppear(). That works fine but when I try to pick something and go back to the update view the .onAppear() gets called again and I loose the picked value.
In the code it looks like this:
import SwiftUI
struct MaterialUpdateView: View {
// Bindings
#State var material: Material
// Form Values
#State var selectedUnit = ""
var body: some View {
VStack(){
List() {
Section(header: Text("MATERIAL")){
// Picker for the Unit
Picker(selection: $selectedUnit, label: Text("Einheit")) {
ForEach(API().units) { unit in
Text("\(unit.name)").tag(unit.name)
}
}
}
}
.listStyle(GroupedListStyle())
}
.onAppear(){
prepareToUpdate()
}
}
func prepareToUpdate() {
self.selectedUnit = self.material.unit
}
}
Does anyone has experience with that problem or am I doing something terribly wrong?
You need to create a custom binding which we will implement in another subview. This subview will be initialised with the binding vars selectedUnit and material
First, make your MaterialUpdateView:
struct MaterialUpdateView: View {
// Bindings
#State var material : Material
// Form Values
#State var selectedUnit = ""
var body: some View {
NavigationView {
VStack(){
List() {
Section(header: Text("MATERIAL")) {
MaterialPickerView(selectedUnit: $selectedUnit, material: $material)
}
.listStyle(GroupedListStyle())
}
.onAppear(){
prepareToUpdate()
}
}
}
}
func prepareToUpdate() {
self.selectedUnit = self.material.unit
}
}
Then, below, add your MaterialPickerView, as shown:
Disclaimer: You need to be able to access your API() from here, so move it or add it in this view. As I have seen that you are re-instanciating it everytime, maybe it is better that you store its instance with let api = API() and then refer to it with api, and even pass it to this view as such!
struct MaterialPickerView: View {
#Binding var selectedUnit: String
#Binding var material : Material
#State var idx: Int = 0
var body: some View {
let binding = Binding<Int>(
get: { self.idx },
set: {
self.idx = $0
self.selectedUnit = API().units[self.idx].name
self.material.unit = self.selectedUnit
})
return Picker(selection: binding, label: Text("Einheit")) {
ForEach(API().units.indices) { i in
Text(API().units[i].name).tag(API().units[i].name)
}
}
}
}
That should do,let me know if it works!

Passing data from extension in SwiftUI

I am building a complex interface in SwiftUI that I need to break into multiple extensions in order to be able to compile the code, but I can't figure out how to pass data between the extension and the body structure.
I made a simple code to explain it :
class Search: ObservableObject {
#Published var angle: Int = 10
}
struct ContentView: View {
#ObservedObject static var search = Search()
var body: some View {
VStack {
Text("\(ContentView.self.search.angle)")
aTest()
}
}
}
extension ContentView {
struct aTest: View {
var body: some View {
ZStack {
Button(action: { ContentView.search.angle = 11}) { Text("Button")}
}
}
}
}
When I press the button the text does not update, which is my issue. I really appreciate any help you can provide.
You can try the following:
struct ContentView: View {
#ObservedObject var search = Search()
var body: some View {
VStack {
Text("\(ContentView.self.search.angle)")
aTest // call as a computed property
}
}
}
extension ContentView {
var aTest: some View { // not a separate `struct` anymore
ZStack {
Button(action: { self.search.angle = 11 }) { Text("Button")}
}
}
}

How do I switch views with swiftUI MacOS?

I found this question - What is the best way to switch views in SwiftUI? - but I have not been able to get the answer to work for me.
struct view4x: View {
#State var goView: Bool = false
var body: some View {
if goView {
view5x(goView1: self.$goView)
} else {
Form {
/* ... */
}
}
}
}
and the button is inside the form:
Button(action: {
self.goView.toggle()
}) {
Text("Catalog")
}
and for my other view I have:
struct view5x: View {
#Binding var goView1: Bool
var body: some View {
Text("TEST")
Button(action: {
self.goView1.toggle()
}) {
Text("Return")
}
}
}
I just get errors that both bodies declare an opaque return type. It does not preview.
Ok, here are similar mistakes in your views. To understand them better to look at View protocol:
#available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
public protocol View {
/// The type of view representing the body of this view.
///
/// When you create a custom view, Swift infers this type from your
/// implementation of the required `body` property.
associatedtype Body : View
/// Declares the content and behavior of this view.
var body: Self.Body { get }
}
so, body is just a computed variable and it should return some View. Mistake in your view5x is that you put into it 2 different views instead 1. The solution here is to embed them into VStack for example:
struct view5x: View{
#Binding var goView1: Bool
var body: some View{
VStack {
Text("TEST")
Button(action: {
self.goView1.toggle()
}) {
Text("Return")
}
}
}
}
The problem it the view4x is similar - it's unclear what view returns body because of if...else statements, I think. You can fix it in the same way:
struct view4x: View {
#State var goView: Bool = false
var body: some View {
VStack {
if goView {
view5x(goView1: $goView)
} else {
Button(action: {
self.goView.toggle()
}) {
Text("Catalog")
}
}
}
}
}
The other way is to say what view should body return if you wrap each of them into AnyView and type return before. In this example changes of goView don't switch views, but you can see the other syntax:
struct view4x: View {
#State var goView: Bool = false
var body: some View {
if goView {
return AnyView(view5x(goView1: $goView))
} else {
return AnyView(Button(action: {
self.goView.toggle()
}) {
Text("Catalog")
})
}
}
}