SkillAgentSearch skills...

Water

Composable SwiftUI

Install / Use

/learn @OpenLyl/Water

README

<h1 align="center">Water</h1> <p align="center"> <a href="https://bmc.link/creatormetasky"><img src="https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png" height="20px"></a> <a href="https://developer.apple.com/swift"><img alt="Swift5" src="https://img.shields.io/badge/language-Swift5-orange.svg"></a> <a href="https://developer.apple.com"><img alt="Platform" src="https://img.shields.io/badge/platform-iOS%20%7C%20macOS%20%7C%20tvOS%20%7C%20watchOS%20%7C-green.svg"></a> <a href="https://swift.org/package-manager/"><img src="https://img.shields.io/badge/SPM-supported-DE5C43.svg?style=flat"></a> <a href="https://discord.gg/rr5PFXEF4n"><img src="https://img.shields.io/discord/942890966652694619?logo=discord" alt="chat on Discord"></a> </p> <h5 align="center">English | <a href="https://github.com/OpenLyl/Water/blob/main/README_CN.md">简体中文</a></h5>

Water - help you to progressively write functional SwiftUI.

func CounterView() -> some View {
    let count = def(value: 0)

    return View {
        Text("\(count.value)")
        HStack {
            Button("+1") {
                count.value += 1
            }
            Button("-1") {
                count.value -= 1
            }
        }
    }
}


Why use Water?

As we all know, SwiftUI is a data-driven responsive UI framework. Apple provides a lot of state management tools, such as: @State@StateObject@Binding@Observable ..., but these tools can be very confusing for a newbie to SwiftUI, what tool to use when exactly? when master all these tools, switch between them also has a big const. finally, when the project gets complex, the screen full of @ and $ symbols make the code difficult to maintain and read.

Now, let's see what @ means in Swift:

  • Attribute: @main@objc, @autoclosure
  • PropertyWrapper: @State, @StateObject
  • Macro: @Observable

For developers, all those @ usages will place a heavy burden on the development mind.

So I am trying to develop this library - Water.

Of course, Water not only solves the above problems, but more importantly guides you through a progressive approach to writing SwiftUI code that will help you step-by-step towards your own standalone project.

Water design for the following purposes:

  • Clear: not require confusing @ symbols
  • Clean: focus on code logic rather than code style
  • Composable: reuse your code use Composable (support MVVM, not recommend)
  • Freedom: not constrain the way you write code (support Redux style, not recommend)
  • Maintainable: easy and visual testing the state logic

Installation

Xcode

From Xcode menu: File > Add Packages...:

https://github.com/OpenLyl/Water

Swift Package Manager

Add the Package url to Package.swift, finally your Package.swift manifest should like below:

let package = Package(
  name: "MyApp",
  dependencies: [
    .package(url: "https://github.com/OpenLyl/Water", .branch("main")),
  ],
  targets: [
    .target(name: "MyApp", dependencies: [
      .product(name: "Water", package: "Water"),
    ]),
  ]
)

Cocoapods

First, add the following entry in your Podfile:

pod 'Water', :git => 'https://github.com/OpenLyl/Water.git', :branch => 'main'

Then run pod install.

Import

Finally, don't forget to import the framework with import Water.

Usage

When using Water, you only need to consider whether your state is a valueobject or an array, define state is like defining variable, so simple.

define value

use def(value: ) to define a value state, because the value is wrapped in a box, so use .value to get or set the value:

func UserView() -> some View {
    let name = def(value: "jack")
    let age = def(value: 20)
    
    return View {
        Text("\(name.value)'s age = \(age.value)")
        Button("change age") {
            age.value += 1
        }
        TextField("input your name", text: name.bindable)
    }
}

define object

use def(object: ) to change object reactivity, support struct and class.

struct User {
    var name: String
    var age: Int
}
func UserView() -> some View {
    let user = def(object: User(name: "jack", age: 20))
    
    return View {
        VStack {
            Text("user.name = \(user.name)")
            Text("user.age = \(user.age)")
            VStack {
                Button("change name") {
                    user.name = "rose"
                }
                Button("change age") {
                    user.age += 1
                }
            }
        }
    }
}

define array

To make array reactivity use def(array: ), as simple as define object.

func NumberListView() -> some View {
    let array = ["1", "2", "3"]
    var nextIndex = array.count + 1

    let items = def(array: array)

    return View {
        VStack {
            LazyVStack {
                ForEach(items, id: \.self) { item in
                    Text("the item = \(item)")
                }
                Text("combined value = \(items.joined(separator: "-|"))")
            }
            HStack(spacing: 16) {
                Button("add item") {
                    nextIndex += 1
                    items.append("\(nextIndex)")
                }
                Button("remove all") {
                    nextIndex = 0
                    items.removeAll()
                }
                Button("clean item") {
                    nextIndex = 3
                    items.replace(with: ["1", "2", "3"])
                }
            }
        }
    }
}

define watch

Water also has the ability to listen for data changes and quickly select useful states by using defWatch.

func WatchEffectView() -> some View {
    let count = def(value: 0)
    let name = def(value: "some name")
    
    defWatchEffect { _ in
        // declare a side effect
        print("trigger watch effect")
    }
    
    defWatch(name) { value, oldValue, _ in
        // when name change do something
        print("name changed = \(value), old name = \(oldValue)")
    }
    
    return View {
        Text("the count = \(count.value)")
        Button("click me change count") {
            count.value += 1
        }
        Text("the name = \(name.value)")
        TextField("name", text: name.bindable)
    }
}

define computed

In most cases, you can use Swift native computed property directly to pick the defined states.

let user = def(object: User(name: "hello", age: 18))

var displayName: String {
    "name is \(user.name)"
}

var displayAge: String {
    "\(user.age) years old"
}

outside of this,Water also provide the cacheable computed property, when there are complex data processing, use defComputed.

func FilterNumbersView() -> some View {
    let showEven = def(value: false)
    let items = def(array: [1, 2, 3, 4, 5, 6])
    
    let evenNumbers = defComputed {
        items.filter { !showEven.value || $0 % 2 == 0}
    }
    
    return View {
        VStack {
            Toggle(isOn: showEven.bindable) {
                Text("Only show even numbers")
            }
            Button("dynamic insert num") {
                let newNumbers = [7, 8, 9, 10]
                items.append(contentsOf: newNumbers)
            }
        }
        .padding(.horizontal, 15)
        List(evenNumbers.value, id: \.self) { num in
            Text("the num = \(num)")
        }
    }
}

nested state

under writing

Composables

Once all the states become reactive, use composable way to extract the data logic is so natural.

useReducer

useReducer allow you code SwiftUI in Redux style, very similar to TCA.

struct CountState {
    var count: Int = 0
}

enum CountAction {
    case increase
    case decrease
}

func countReducer(state: inout CountState, action: CountAction) {
    switch action {
    case .increase:
        state.count += 1
    case .decrease:
        state.count -= 1
    }
}

func ReducerCounterView() -> some View {
    let (useCountState, dispatch) = useReducer(CountState(), countReducer)

    return View {
        Text("the count = \(useCountState().count)")
        HStack {
            Button("+1") {
                dispatch(.increase)
            }
            Button("-1") {
                dispatch(.decrease)
            }
        }
    }
}

useStore

useStore will be more powerful than useReducer, it's still under development.

let useCounterStore = defStore("counter") {
    let count = def(value: 0)

    func increment() {
        count.value += 1
    }

    func decrement() {
        count.value -= 1
    }

    return (count, increment, decrement)
}

func StoreCountView() -> some View {
    let store = useCounterStore()

    return View {
        Text("the count = \(store().count)")
        HStack {
            Button("+1") {
                store.increment()
            }
            Button("-1") {
                store.decrement()
            }
        }
    }
}

useFetch

useFetch provides the ability to send http restful requests and final fetch the network result data, now is a simple version, it will be more flexible and powerful in the future.

func UseFetchView() -> some View {
    let (isFetching, error, data) = useFetch(url: "https://httpbin.org/get")
    
    return View {
        VStack {
            Text(isFetching.value ? "is fetching" : "fetch completed")
            if let 
View on GitHub
GitHub Stars92
CategoryDevelopment
Updated4mo ago
Forks4

Languages

Swift

Security Score

92/100

Audited on Nov 18, 2025

No findings