Add projects

This commit is contained in:
Ivan Vorobei
2019-06-07 09:02:12 +03:00
parent f385cd4929
commit fce7627f56
42 changed files with 2011 additions and 0 deletions

69
Examples/React Meets SwiftUI/.gitignore vendored Executable file
View 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

View 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()
}
}
}
```

View 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)
}
}
}
```

View 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)
}
}
```

View 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)")
}
}
```

View 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)

View 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")
}
}
}
}
```