mirror of
https://github.com/ivanvorobei/SwiftUI.git
synced 2026-03-23 18:01:16 +01:00
Add projects
This commit is contained in:
69
Examples/React Meets SwiftUI/.gitignore
vendored
Executable file
69
Examples/React Meets SwiftUI/.gitignore
vendored
Executable file
@@ -0,0 +1,69 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Typescript v1 declaration files
|
||||
typings/
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
|
||||
# gatsby files
|
||||
.cache/
|
||||
public
|
||||
|
||||
# Mac files
|
||||
.DS_Store
|
||||
|
||||
# Yarn
|
||||
yarn-error.log
|
||||
.pnp/
|
||||
.pnp.js
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
55
Examples/React Meets SwiftUI/Children.md
Executable file
55
Examples/React Meets SwiftUI/Children.md
Executable file
@@ -0,0 +1,55 @@
|
||||
## Children
|
||||
|
||||
I'm still trying to really nail this one down, but it seems possible to pass children to child views in SwiftUI. This example shows a common `Layout`, `Content` composition pattern which I commonly use in React, and is the key to developing reusable, flexible components.
|
||||
|
||||
### React
|
||||
|
||||
```jsx
|
||||
import React from "react";
|
||||
|
||||
function Page() {
|
||||
return (
|
||||
<Layout>
|
||||
<span>This is the page content</span>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
||||
function Layout({ children }) {
|
||||
return (
|
||||
<div>
|
||||
<span>This is the layout</span>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### SwiftUI
|
||||
|
||||
The SwiftUI version seems harder to type. Currently, the below example only accepts a `Text` child. Does anyone know how to make this accept any view type?
|
||||
|
||||
```swift
|
||||
import SwiftUI
|
||||
|
||||
struct Page : View {
|
||||
var body: some View {
|
||||
Layout() {
|
||||
Text("This is the page content")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Layout : View {
|
||||
|
||||
var content: () -> Text
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
Text("This is the layout")
|
||||
content()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
61
Examples/React Meets SwiftUI/CloneElement.md
Executable file
61
Examples/React Meets SwiftUI/CloneElement.md
Executable file
@@ -0,0 +1,61 @@
|
||||
## cloneElement
|
||||
|
||||
You'll sometimes use React's `cloneElement` to provide additonal attributes to a child component - i.e, to add an event handler, className, etc. You can achieve similar behaviour in SwiftUI.
|
||||
|
||||
### React
|
||||
|
||||
```jsx
|
||||
import React from "react";
|
||||
|
||||
export function PageElement() {
|
||||
return (
|
||||
<div>
|
||||
<LayoutElement>
|
||||
<span>This is the page content</span>
|
||||
</LayoutElement>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function LayoutElement({ children }) {
|
||||
return (
|
||||
<div>
|
||||
<span>This is the layout</span>
|
||||
{React.cloneElement(children, {
|
||||
style: {
|
||||
fontWeight: "bold",
|
||||
fontSize: "2rem"
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Swift
|
||||
|
||||
```swift
|
||||
import SwiftUI
|
||||
|
||||
struct PageElement : View {
|
||||
var body: some View {
|
||||
LayoutElement() {
|
||||
Text("This is the page content")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct LayoutElement : View {
|
||||
|
||||
var content: () -> Text
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
Text("This is the layout")
|
||||
content()
|
||||
.bold()
|
||||
.font(.largeTitle)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
125
Examples/React Meets SwiftUI/ComponentDidMount.md
Executable file
125
Examples/React Meets SwiftUI/ComponentDidMount.md
Executable file
@@ -0,0 +1,125 @@
|
||||
## ComponentDidMount
|
||||
|
||||
With React it's common to perform one-time operations (such as async fetches to a server) in the `componentDidMount` function or the `useEffect` hook. You can replicate this behaviour using the `onAppear` and `onDisappear` callbacks on any View. This means that you can actually have multiple `onAppear` callbacks attached to multiple views contained within your `body` function. Your parent view can also attach these callbacks to custom child views.
|
||||
|
||||
The below example mounts and unmounts the counter readout when it reaches a certain value. We log when the mount/unmount happens.
|
||||
|
||||
### React
|
||||
|
||||
```jsx
|
||||
import React from "react";
|
||||
|
||||
export function Counter() {
|
||||
const [count, setCount] = React.useState(0);
|
||||
|
||||
function increment() {
|
||||
setCount(count + 1);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button onClick={increment}>Increment</button>
|
||||
{(count < 10 || count > 12) && <ChildCounter count={count} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function ChildCounter({ count }) {
|
||||
React.useEffect(() => {
|
||||
console.log("mounted");
|
||||
|
||||
return () => {
|
||||
console.log("unmounting");
|
||||
};
|
||||
}, []);
|
||||
|
||||
return <div>{count}</div>;
|
||||
}
|
||||
```
|
||||
|
||||
### Swift
|
||||
|
||||
Note that this only approximates the React behaviour. I'm not sure what the `appear` / `mount` distinction implies, but it seems to provide similar utility.
|
||||
|
||||
```swift
|
||||
import SwiftUI
|
||||
|
||||
struct Counter : View {
|
||||
@State var count = 0
|
||||
|
||||
func increment () {
|
||||
count += 1
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
|
||||
VStack {
|
||||
Button(action: increment) {
|
||||
Text("Increment")
|
||||
}
|
||||
if count < 10 || count > 12 {
|
||||
ChildCounter(count: count)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
struct ChildCounter : View {
|
||||
var count: Int
|
||||
|
||||
func onMount () {
|
||||
print("on mount")
|
||||
}
|
||||
|
||||
func onUnmount () {
|
||||
print("on unmount")
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Text("\(count)")
|
||||
.onAppear(perform: onMount)
|
||||
.onDisappear(perform: onUnmount)
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
You can attach multiple `onAppear` and `onDisappear` callbacks in your `body` function:
|
||||
|
||||
```swift
|
||||
struct Counter : View {
|
||||
@State var count = 0
|
||||
|
||||
func increment () {
|
||||
count += 1
|
||||
}
|
||||
|
||||
func mount () {
|
||||
print("Child mount")
|
||||
}
|
||||
|
||||
func unmount () {
|
||||
print("Child unmount")
|
||||
}
|
||||
|
||||
func parentMount () {
|
||||
print("parent mount")
|
||||
}
|
||||
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
Button(action: increment) {
|
||||
Text("Increment")
|
||||
}
|
||||
if count < 10 || count > 12 {
|
||||
ChildCounter(count: count)
|
||||
.onAppear(perform: mount)
|
||||
.onDisappear(perform: unmount)
|
||||
}
|
||||
}
|
||||
.onAppear(perform: parentMount)
|
||||
}
|
||||
}
|
||||
```
|
||||
70
Examples/React Meets SwiftUI/Context.md
Executable file
70
Examples/React Meets SwiftUI/Context.md
Executable file
@@ -0,0 +1,70 @@
|
||||
## Context
|
||||
|
||||
You can replicate the context functionality found in React by using a combination of `BindableObject`, `Combine` and defining an `environmentObject` for a View. You create a class that adheres to the `BindableObject` and provide that as an argument to `environmentObject` when initiating your parent view. Any child of that parent view can then access the class using `@EnvironmentObject`.
|
||||
|
||||
### React
|
||||
|
||||
```jsx
|
||||
import React, { useContext } from "react";
|
||||
|
||||
const Session = React.createContext({ name: "" });
|
||||
|
||||
export function ContextProvider() {
|
||||
return (
|
||||
<Session.Provider value={{ name: "Bento" }}>
|
||||
<Parent />
|
||||
</Session.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function Parent() {
|
||||
return <Child />;
|
||||
}
|
||||
|
||||
export function Child() {
|
||||
const session = useContext(Session);
|
||||
|
||||
return <span>Hello {session.name}</span>;
|
||||
}
|
||||
```
|
||||
|
||||
### SwiftUI
|
||||
|
||||
```swift
|
||||
import SwiftUI
|
||||
import Combine
|
||||
|
||||
final class Session: BindableObject {
|
||||
let didChange = PassthroughSubject<Session, Never>()
|
||||
|
||||
var name: String {
|
||||
didSet { didChange.send(self) }
|
||||
}
|
||||
|
||||
init(name: String) {
|
||||
self.name = name
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct ContextProvider : View {
|
||||
var body: some View {
|
||||
Parent().environmentObject(Session(name: "Bento"))
|
||||
}
|
||||
}
|
||||
|
||||
struct Parent : View {
|
||||
var body: some View {
|
||||
Child()
|
||||
}
|
||||
}
|
||||
|
||||
struct Child : View {
|
||||
|
||||
@EnvironmentObject var session: Session
|
||||
|
||||
var body: some View {
|
||||
Text("Hello \(session.name)")
|
||||
}
|
||||
}
|
||||
```
|
||||
13
Examples/React Meets SwiftUI/README.md
Executable file
13
Examples/React Meets SwiftUI/README.md
Executable file
@@ -0,0 +1,13 @@
|
||||
# React Meets SwiftUI
|
||||
|
||||
I'm looking to learn SwiftUI over the coming weeks and will be using this repository to record various common React design patterns implemented in SwiftUI.
|
||||
|
||||
Please note: I'm a total newbie when it comes to Swift, so please contribute where you can to fix my inevitable errors.
|
||||
|
||||
### Examples
|
||||
|
||||
- [Managing State](State.md)
|
||||
- [Using Context](Context.md)
|
||||
- [Using Children](Children.md)
|
||||
- [Mounting and Unmounting Callbacks](ComponentDidMount.md)
|
||||
- [Emulating CloneElement](CloneElement.md)
|
||||
112
Examples/React Meets SwiftUI/State.md
Executable file
112
Examples/React Meets SwiftUI/State.md
Executable file
@@ -0,0 +1,112 @@
|
||||
## Managing State
|
||||
|
||||
With SwiftUI you can create state in views and pass that state to child views, much as you would in React. You can also pass functions as props which allow child views to control state in parent components.
|
||||
|
||||
The example below creates a simple counter. The `ContentView` manages the `count` state, while the child view renders that count and asks the parent to increment.
|
||||
|
||||
### React
|
||||
|
||||
```jsx
|
||||
import React from "react";
|
||||
|
||||
export function ContentView() {
|
||||
const [count, setCount] = useState(0);
|
||||
|
||||
function increment() {
|
||||
setCount(count + 1);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div>Press the button below</div>
|
||||
<ChildView count={count} increment={increment} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function ChildView({ count, increment }) {
|
||||
return (
|
||||
<div>
|
||||
<div>{coun t}</div>
|
||||
<button onClick={increment}>Increment</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### SwiftUI
|
||||
|
||||
```swift
|
||||
import SwiftUI
|
||||
|
||||
struct ContentView : View {
|
||||
@State var count = 0
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 1.0) {
|
||||
Text("Press the button below")
|
||||
ChildView(
|
||||
counter: count,
|
||||
increment: increment
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func increment() {
|
||||
count += 1
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct ChildView : View {
|
||||
var counter: Int
|
||||
var increment: () -> Void
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
Text("\(counter)")
|
||||
Button(action: increment) {
|
||||
Text("Increment")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
With SwiftUI you can use `@Binding` to enable child views to alter state provided by parent views. Notice that we prepend the `$` to our count value to pass its binding value. Our child can then alter the `count` state itself without supplying a callback to the parent.
|
||||
|
||||
```swift
|
||||
import SwiftUI
|
||||
|
||||
struct ContentView : View {
|
||||
@State var count = 0
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 1.0) {
|
||||
Text("Press the button below")
|
||||
ChildView(
|
||||
counter: $count
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct ChildView : View {
|
||||
@Binding var counter: Int
|
||||
|
||||
func increment () {
|
||||
counter += 1
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
Text("\(counter)")
|
||||
Button(action: increment) {
|
||||
Text("Increment")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user