Last active
February 27, 2024 03:01
-
-
Save pitt500/4abcc67a2fbc9d868b4a53dc6176efd8 to your computer and use it in GitHub Desktop.
Demo explaining Result Builders, check out this link for more context: https://youtu.be/kZ7JPFUVv1w
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Created by Pedro Rojas (aka Pitt) | |
// Link to learn more about this code: https://youtu.be/kZ7JPFUVv1w | |
import SwiftUI | |
import WebKit | |
@resultBuilder | |
struct HtmlBuilder { | |
static func buildBlock() -> [HtmlTag] { | |
[] | |
} | |
static func buildBlock(_ components: HtmlTag...) -> [HtmlTag] { | |
components | |
} | |
static func buildBlock(_ components: [HtmlTag]...) -> [HtmlTag] { | |
components.flatMap { $0 } | |
} | |
static func buildArray(_ components: [[HtmlTag]]) -> [HtmlTag] { | |
components.flatMap { $0 } | |
} | |
static func buildExpression(_ expression: HtmlTag) -> [HtmlTag] { | |
[expression] | |
} | |
static func buildOptional(_ component: [HtmlTag]?) -> [HtmlTag] { | |
component ?? [] | |
} | |
static func buildEither(first component: [HtmlTag]) -> [HtmlTag] { | |
component | |
} | |
static func buildEither(second component: [HtmlTag]) -> [HtmlTag] { | |
component | |
} | |
} | |
// MARK: Html Tags | |
protocol HtmlTag { | |
func render() -> String | |
} | |
struct Title: HtmlTag { | |
let title: String | |
init(_ title: String) { | |
self.title = title | |
} | |
func render() -> String { | |
"<title>" + title + "</title>" | |
} | |
} | |
struct Body: HtmlTag { | |
let children: [HtmlTag] | |
init(@HtmlBuilder _ content: () -> [HtmlTag]) { | |
self.children = content() | |
} | |
func render() -> String { | |
"<body>" + | |
children | |
.map { $0.render() } | |
.joined() + | |
"</body>" | |
} | |
} | |
struct Head: HtmlTag { | |
let children: [HtmlTag] | |
init(@HtmlBuilder _ content: () -> [HtmlTag]) { | |
self.children = content() | |
} | |
func render() -> String { | |
"<head>" + | |
children | |
.map { $0.render() } | |
.joined() + | |
"</head>" | |
} | |
} | |
struct Meta: HtmlTag { | |
let name: String | |
let content: String | |
func render() -> String { | |
"<meta name=\"\(name)\" content=\"\(content)\">" | |
} | |
} | |
struct P: HtmlTag { | |
let text: String | |
init(_ text: String) { | |
self.text = text | |
} | |
func render() -> String { | |
"<p>" + text + "</p>" | |
} | |
} | |
struct H1: HtmlTag { | |
let text: String | |
init(_ text: String) { | |
self.text = text | |
} | |
func render() -> String { | |
"<h1>" + text + "</h1>" | |
} | |
} | |
struct Table: HtmlTag { | |
let rows: [HtmlTag] | |
init(@HtmlBuilder _ content: () -> [HtmlTag]) { | |
self.rows = content() | |
} | |
func render() -> String { | |
"<table>" + | |
rows | |
.map { $0.render() } | |
.joined() + | |
"</table>" | |
} | |
} | |
struct Tr: HtmlTag { | |
let columns: [HtmlTag] | |
init(@HtmlBuilder _ content: () -> [HtmlTag]) { | |
self.columns = content() | |
} | |
func render() -> String { | |
"<tr>" + | |
columns | |
.map { $0.render() } | |
.joined() + | |
"</tr>" | |
} | |
} | |
struct Td<Value: CustomStringConvertible>: HtmlTag { | |
let data: Value | |
init(_ data: Value) { | |
self.data = data | |
} | |
func render() -> String { | |
"<td>" + data.description + "</td>" | |
} | |
} | |
struct Th: HtmlTag { | |
let title: String | |
init(_ title: String) { | |
self.title = title | |
} | |
func render() -> String { | |
"<th>" + title + "</th>" | |
} | |
} | |
struct Img: HtmlTag { | |
private var attributes: [Attribute] = [] | |
init(url: String) { | |
self.attributes.append(Attribute(key: "src", value: url)) | |
} | |
func render() -> String { | |
return "<img " + buildAttributes() + ">" | |
} | |
private func buildAttributes() -> String { | |
var attributesString = "" | |
for attribute in attributes { | |
attributesString.append(attribute.key + "=" + "\"\(attribute.value)\" ") | |
} | |
print(attributesString) | |
return attributesString | |
} | |
} | |
extension Img { | |
func alt(text: String) -> Self { | |
var newImage = self | |
newImage.attributes.append(Attribute(key: "alt", value: text)) | |
return newImage | |
} | |
func style(width: Int?, height: Int?) -> Self { | |
var newImage = self | |
if let width { | |
newImage.attributes.append(Attribute(key: "width", value: "\(width)")) | |
} | |
if let height { | |
newImage.attributes.append(Attribute(key: "height", value: "\(height)")) | |
} | |
return newImage | |
} | |
} | |
struct Attribute { | |
let key: String | |
let value: String | |
} | |
// MARK: WebView | |
struct WebView: UIViewRepresentable { | |
@HtmlBuilder var content: () -> [HtmlTag] | |
func makeUIView(context: Context) -> WKWebView { | |
let webView = WKWebView() | |
return webView | |
} | |
func updateUIView(_ webView: WKWebView, context: Context) { | |
let string = content() | |
.map { $0.render() } | |
.joined() | |
let fullHtml = "<html>" + string + "</html>" | |
print(fullHtml) | |
webView.loadHTMLString( | |
fullHtml, | |
baseURL: nil | |
) | |
} | |
} | |
func isValidStringURL(_ string: String) -> Bool { | |
URL(string: string) != nil | |
} | |
let imageUrl = "https://img.pokemondb.net/artwork/large/flareon.jpg" | |
struct WebView_Previews: PreviewProvider { | |
static var previews: some View { | |
WebView { | |
Head { | |
Title("My Title") | |
Meta( | |
name: "viewport", | |
content: "width=device-width, initial-scale=1.0, maximum-scale=5.0, user-scalable=1" | |
) | |
} | |
Body { | |
H1("This is a heading!") | |
if isValidStringURL(imageUrl) { | |
P("Flareon") | |
Img(url: imageUrl) | |
.alt(text: "This is flareon") | |
.style(width: 100, height: 100) | |
} else { | |
P("No image") | |
} | |
P("Moves learnt by level up!") | |
Table { | |
Tr { | |
Th("Lv.") | |
Th("Move") | |
Th("Type") | |
Th("Cat.") | |
Th("Power") | |
Th("Acc.") | |
} | |
for row in Move.sample { | |
Tr { | |
Td(row.level) | |
Td(row.name) | |
Td(row.type) | |
Td(row.category.rawValue) | |
Td(row.power) | |
Td(row.accuracy) | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
// Mark: Moveset | |
enum Category: String { | |
case special = "Special" | |
case physical = "Physical" | |
case status = "Status" | |
} | |
struct Move { | |
let level: Int | |
let name: String | |
let type: String | |
let category: Category | |
let power: String | |
let accuracy: String | |
} | |
extension Move { | |
static let sample: [Self] = [ | |
.init(level: 1, name: "Baton Pass", type: "Normal", category: .status, power: "--", accuracy: "--"), | |
.init(level: 1, name: "Charm", type: "Fairy", category: .status, power: "--", accuracy: "100"), | |
.init(level: 1, name: "Tackle", type: "Normal", category: .physical, power: "40", accuracy: "100"), | |
.init(level: 25, name: "Bite", type: "Dark", category: .physical, power: "60", accuracy: "100"), | |
.init(level: 30, name: "Fire Fang", type: "Fire", category: .physical, power: "65", accuracy: "95"), | |
.init(level: 30, name: "Fire Spin", type: "Fire", category: .special, power: "35", accuracy: "85"), | |
.init(level: 45, name: "Scary Face", type: "Normal", category: .status, power: "--", accuracy: "100"), | |
.init(level: 50, name: "Flare Blitz", type: "Fire", category: .physical, power: "120", accuracy: "100"), | |
] | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment