Swift #functionBuilder doesn't recognize variadic elements - ios

I'm using a function builder in my project and am having problems implementing buildIf. Here's the builder:
#_functionBuilder
class TableSectionBuilder {
static func buildBlock(_ children: TableGroup...) -> [TableGroup] {
children
}
static func buildBlock(_ children: [TableGroup]...) -> [TableGroup] {
children.flatMap { $0 }
}
static func buildExpression(_ child: TableGroup) -> [TableGroup] {
[child]
}
static func buildIf(_ children: [TableGroup]?) -> [TableGroup] {
return children ?? []
}
}
Here's an example of how I'd like to use it (note: Text is a custom object, similar to SwiftUI's Text)
func test() {
Self.section(identifier: "") {
Text("")
Text("")
if true {
Text("")
}
}
}
Unfortunately, this doesn't compile, though this compiles:
func test() {
Self.section(identifier: "") {
[Text(""),
Text("")]
if true {
Text("")
}
}
}
It looks to me that the function builder cannot properly map from an array to a variadic list of items. Also, removing the if in the first example makes it compile.
Ideas?

This is a limit of Swift today. It is inherently why SwiftUI views are limited to 10 right now, and you see methods like combineLatest3 and combineLatest4 in the Combine framework.
According to the swift forums, variadic function builders is something being worked on and may be included in Swift 6.

Related

Wrong swift extension function is called [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 8 months ago.
Improve this question
When the getBadge() method is called from the view, why it uses the function from the extension without Status == OpenAccessPointState condition, if the Status.self is OpenAccessPointState in runtime?
Here is the code:
protocol BadgeStateViewRepresentable: Statusable {
func getBadge() -> BadgeWithText?
}
protocol Statusable {
associatedtype Status: AccessPointState
var status: Status { get }
}
extension View where Self: BadgeStateViewRepresentable {
func getBadge() -> BadgeWithText? {
return nil
}
}
extension View where Self: BadgeStateViewRepresentable, Status == OpenAccessPointState {
func getBadge() -> BadgeWithText? {
return BadgeWithText()
}
}
struct SomeDeviceDetailsView: View, BadgeStateViewRepresentable {
var status: some AccessPointState {
return OpenAccessPointState()
}
var body: some View {
getBadge()
}
}
Is there way to make this working?
If you actually declare the type of status explicitly, then it works as expected:
var status: OpenAccessPointState {
return OpenAccessPointState()
}
This is because opaque types are opaque. SomeDeviceDetailsView.Status is some AccessPointState, and you cannot "see through" what specific AccessPointState it is. Therefore, it does not fulfil the requirement Status == OpenAccessPointState.
This has nothing to do with what happens "at runtime". SomeDeviceDetailsView.Status is a type, not a variable, and it doesn't become something different at runtime. compared to at compile time.
A simpler example:
protocol Foo {
associatedtype MyType
func foo() -> MyType
}
extension Foo where MyType == Int {
func bar() { }
}
struct Impl: Foo {
func foo() -> some CustomStringConvertible {
1
}
}
// Referencing instance method 'bar()' on 'Foo' requires the types 'some CustomStringConvertible' and 'Int' be equivalent
Impl().bar()
If you are trying to dynamically dispatch getBadge, you should put it in each of the status types, and get rid of the Statusable protocol whose purpose becomes rather unclear. Example:
protocol AccessPointState {
func getBadge() -> BadgeWithText?
}
extension AccessPointState {
func getBadge() -> BadgeWithText? {
nil
}
}
struct OpenAccessPointState: AccessPointState {
func getBadge() -> BadgeWithText? {
BadgeWithText()
}
}
struct SomeOtherState: AccessPointState {}
struct SomeDeviceDetailsView: View {
var status: AccessPointState {
if Bool.random() {
return OpenAccessPointState()
} else {
return SomeOtherState()
}
}
var body: some View {
status.getBadge()
}
}

ternary operations for labels does not work

I am trying to assign a custom label style based on my icon alignment value, that is left or right. if it is left, the icon comes before text, if it is right the icon comes after it. These are my custom label styles:
var iconAlignment: IconAlignment
enum IconAlignment {
case left
case right
}
struct IconFirstLabelStyle: LabelStyle {
func makeBody(configuration: Configuration) -> some View {
HStack {
configuration.icon
configuration.title
}
}
}
struct IconFirstLabelStyle: LabelStyle {
func makeBody(configuration: Configuration) -> some View {
HStack {
configuration.title
configuration.icon
}
}
}
I want to do this now:
Label { Text("hi") } icon : {Image(systemName: "plus")}
.labelStyle(iconAlignment == .left ? IconFirstLabelStyle() : TextFirstLabelStyle())
but this causes an issue and Xcode tells me these are two different structs, even though they are inheriting the same protocol. Is there any way to use ternary here, or should I go a long approach with if else?
Ternary operator requires both parts to be of the same type.
A simple solution for this is just to use one style type and inject condition via argument, like
Label { Text("hi") } icon : {Image(systemName: "plus")}
.labelStyle(IconAlignedLabelStyle(alignIcon: iconAlignment))
struct IconAlignedLabelStyle: LabelStyle {
var alignIcon = IconAlignment.left // << default one !!
func makeBody(configuration: Configuration) -> some View {
HStack {
if let alignIcon == .left {
configuration.icon
configuration.title
} else {
configuration.title
configuration.icon
}
}
}
}
You say "even though they are inheriting the same protocol"—that does not display the understanding that some, and the ternary operator, each require one type. Such as this:
labelStyle(
iconAlignment == .left
? AnyStyle(IconFirstLabelStyle())
: AnyStyle(TextFirstLabelStyle())
)
import SwiftUI
/// A type-erased "`Style`".
public struct AnyStyle<Configuration> {
private let makeBody: (Configuration) -> AnyView
}
// MARK: - public
public extension AnyStyle {
init(makeBody: #escaping (Configuration) -> some View) {
self.makeBody = { .init(makeBody($0)) }
}
func makeBody(configuration: Configuration) -> some View {
makeBody(configuration)
}
}
// MARK: - LabelStyle
extension AnyStyle: LabelStyle where Configuration == LabelStyleConfiguration {
public init(_ style: some LabelStyle) {
self.init(makeBody: style.makeBody)
}
}
// MARK: - ButtonStyle
extension AnyStyle: ButtonStyle where Configuration == ButtonStyleConfiguration {
public init(_ style: some ButtonStyle) {
self.init(makeBody: style.makeBody)
}
}
// etc.

How to create an enum based .textStyle(.title) modifier for Text(...) components in SwiftUI?

I want to implement a modifier setting for Texts in a similar way as it already exists for Buttons.
Aka:
Button( ... )
.buttonStyle(.plain) // <-- .plain and not PlainStyle()
Problem
Of course I cannot use an opaque which is not really the same. If it would be a View I could wrap it in an AnyView but for ViewModifiers I need another solution.
Error: Function declares an opaque return type,but the return statements in its body do not have matching underlying types
Maybe it is a bonus idea to have something like a .textStyle(.title) modifier but in my eyes, it could reduce my code to write enormously.
Source
struct TitleStyle: ViewModifier {
func body(content: Content) -> some View {
...
}
}
struct BodyStyle: ViewModifier {
func body(content: Content) -> some View {
...
}
}
enum TextStyle {
case title
case body
// Error: Function declares an opaque return type,
// but the return statements in its body do not have matching underlying types
var modifier: some ViewModifier {
switch self
{
case .title: return TitleStyle()
case .body: return BodyStyle()
}
}
}
It works different way. As all this is around generics we need to restrict declarations for known concrete types.
So, having TitleStyle and BodyStyle declared and concrete, we can specify
extension ViewModifier where Self == TitleStyle {
static var title: TitleStyle { TitleStyle() }
}
extension ViewModifier where Self == BodyStyle {
static var body: BodyStyle { BodyStyle() }
}
and then declare extension to use above like
extension View {
func textStyle<Style: ViewModifier>(_ style: Style) -> some View {
ModifiedContent(content: self, modifier: style)
}
}
so as a result we can do as demo
struct Demo_Previews: PreviewProvider {
static var previews: some View {
Text("Demo")
.textStyle(.title)
}
}
Prepared with Xcode 13.4 / iOS 15.5
Test module in GitHub

SwiftUI - How can I call a Function in my View?

I am using SwiftUI/Swift for a week now, and I love it. Now I have a problem. I want to call a Function from my View, but I get this Error
Type '()' cannot conform to 'View'; only struct/enum/class types can conform to protocols
This is the Code:
struct testView: View {
var body: some View {
VStack {
Text("TextBox")
Text("SecondTextBox")
self.testFunction()
}
}
func testFunction() {
print("This is a Text.")
}
}
I don't get it. In other languages its much simpler and could work that way. Can anybody help me please? Swift is pretty new to me :D
Meanwhile here are the places (not all) where/how you can call a function
init() {
self.testFunction() // non always good, but can
}
var body: some View {
self.testFunction() // 1)
return VStack {
Text("TextBox")
.onTapGesture {
self.testFunction() // 2)
}
Text("SecondTextBox")
}
.onAppear {
self.testFunction() // 3)
}
.onDisappear {
self.testFunction() // 4)
}
}
... and so on
An additional method:
Testing with Swift 5.8, you can also stick in a let _ = self.testFunction().
eg
(this is extra-contrived so that it's possible to see the effect in Preview, because print() doesn't happen in Preview, at least for me)
import SwiftUI
class MyClass {
var counter = 0
}
struct ContentView: View {
var myClass: MyClass
var body: some View {
VStack {
Text("TextBox counter = \(myClass.counter)")
// v--------------------------------------------
//
let _ = self.testFunction() // compiles happily
// self.testFunction() // does not compile
//
// ^--------------------------------------------
Text("TextBox counter = \(myClass.counter)")
}
}
func testFunction() {
print("This is a Test.")
myClass.counter += 1
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView(myClass: MyClass())
}
}
Using Swift 5.3 and Xcode 12.4
I use a little extension to debug inside the Views (VStack in the example), e.g. to inspect a geometryReader.size. It could be used to call any function in the View as follows:
NOTE: For debugging purposes only. I don't recommend including code like this in any production code
VStack {
Text.console("Hello, World")
Text("TEXT"
}
extension Text {
static func console<T>(_ value: T) -> EmptyView {
print("\(value)")
return EmptyView()
}
}
This will print "Hello, World" to the console.....
There is a reason, when writing in swift UI (iOS Apps), that there is a View protocol that needs to be followed. Calling functions from inside the structure is not compliant with the protocol.
The best thing you can do is the .on(insertTimeORAction here).
Read more about it here

How to return a View type from a function in Swift UI

I am trying to create a somewhat elegant navigation system for my App. Below is a function that attempts to return a View type. This does not compile with:
func getView(view: String) -> View {
switch view {
case "CreateUser":
return CreateNewsView()
default:
return nil
}
}
The above results in a compile error: Protocol 'View' can only be used as a generic constraint because it has Self or associated type requirements
Thank you for your help.
As of Swift 5.3 #hồng-phúc Answer is somehow right, just needs adding the #ViewBuilder Property explicitly.
#ViewBuilder func getView(view: String) -> some View {
switch view {
case "CreateUser":
Text(view)
case "Abc":
Image("Abc")
default:
EmptyView()
}
}
Side note: Please avoid using String Literals. Better use an enum.
I managed to fix this by using the AnyView() wrapper:
func getView(view: String?) -> AnyView {
switch view {
case "CreateUser":
return AnyView(CreateNewsView())
default:
return AnyView(EmptyView())
}
}
Making AnyView() wrapper
func getView(view: String?) -> AnyView {
if view == "CreateUser" {
return AnyView(CreateNewsView())
}
return AnyView(EmptyView())
}
You should return some View
EX:
func getView(view: String) -> some View {
return YourView()
}
for more detail a bout some View, view this
I use the extension .eraseToAnyView () to make func with some View easier:
extension View {
public function eraseToAnyView () -> AnyView {
AnyView (on its own)
}
}
The final solution will look like this:
func getView (view: String?) -> AnyView {
if view == "CreateUser" {
return CreateNewsView().eraseToAnyView()
}
return EmptyView().eraseToAnyView()
}
I think this is the most concise solution.

Resources