Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 0 additions & 9 deletions Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 1 addition & 5 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ let package = Package(

// --- Dependencies ---
dependencies: [
.package(url: "https://github.com/SwiftyJSON/SwiftyJSON.git", from: "5.0.1"),
.package(url: "https://github.com/pointfreeco/swift-snapshot-testing", from: "1.17.0"),
.package(url: "https://github.com/WeTransfer/Mocker.git", .upToNextMajor(from: "3.0.0")),
],
Expand All @@ -30,9 +29,6 @@ let package = Package(
// Main library target
.target(
name: "BuilderIO",
dependencies: [
"SwiftyJSON",
],
resources: [
.process("Resources/Fonts")
]
Expand All @@ -51,4 +47,4 @@ let package = Package(
]
),
]
)
)
3 changes: 2 additions & 1 deletion Sources/BuilderIO/Components/BuilderBlock.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ struct BuilderBlock: View {
// Only checking links for now, can be expanded to cover events in the future
let isTappable =
component?.name == BuilderComponentType.coreButton.rawValue
|| !(component?.options?["Link"].isEmpty ?? true) || !(child.linkUrl?.isEmpty ?? true)
|| !(component?.options?.dictionaryValue?["Link"]?.stringValue?.isEmpty ?? true)
|| !(child.linkUrl?.isEmpty ?? true)

let builderAction: BuilderAction? =
(isTappable)
Expand Down
43 changes: 32 additions & 11 deletions Sources/BuilderIO/Components/BuilderColumns.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import Foundation
import SwiftUI
import SwiftyJSON

struct BuilderColumns: BuilderViewProtocol {
static let componentType: BuilderComponentType = .columns
Expand All @@ -15,23 +14,45 @@ struct BuilderColumns: BuilderViewProtocol {
init(block: BuilderBlockModel) {
self.block = block

if let jsonString = block.component?.options?["columns"].rawString(),
let jsonData = jsonString.data(using: .utf8)
self.columns = []

if let columnsAnyCodableArray = block.component?.options?
.dictionaryValue?["columns"]?
.arrayValue
{

let decoder = JSONDecoder()
do {
self.columns = try decoder.decode([BuilderContentData].self, from: jsonData)
} catch {
self.columns = []
var decodedColumns: [BuilderContentData] = []

// Iterate through each AnyCodable element in the array
for anyCodableElement in columnsAnyCodableArray {
do {
// Convert the AnyCodable element back into Data
// (This is necessary because JSONDecoder works with Data)
let elementData = try JSONEncoder().encode(anyCodableElement)

// Decode that Data into a BuilderContentData instance
let column = try decoder.decode(BuilderContentData.self, from: elementData)
decodedColumns.append(column)
} catch {
// Handle error for a specific element if it can't be decoded
print("Error decoding individual BuilderContentData from AnyCodable element: \(error)")
// You might choose to append a default empty BuilderContentData,
// or simply skip this element, as we are doing here.
}
}
self.columns = decodedColumns

} else {
self.columns = []
print("Could not find or access 'columns' array in component options.")
}

self.space = block.component?.options?["space"].doubleValue ?? 0
self.stackColumns = !(block.component?.options?["stackColumnsAt"] == "never" ?? false)
self.space = block.component?.options?.dictionaryValue?["space"]?.doubleValue ?? 0
self.stackColumns =
!((block.component?.options?.dictionaryValue?["stackColumnsAt"]?.stringValue == "never")
?? false)
self.reverseColumnsWhenStacked =
block.component?.options?["reverseColumnsWhenStacked"].boolValue ?? false
block.component?.options?.dictionaryValue?["reverseColumnsWhenStacked"]?.boolValue ?? false

}

Expand Down
11 changes: 7 additions & 4 deletions Sources/BuilderIO/Components/BuilderImage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,18 @@ struct BuilderImage: BuilderViewProtocol {

init(block: BuilderBlockModel) {
self.block = block
self.imageURL = URL(string: block.component?.options?["image"].string ?? "")
if let ratio = block.component?.options?["aspectRatio"].float {
self.imageURL = URL(
string: block.component?.options?.dictionaryValue?["image"]?.stringValue ?? "")
if let ratio = block.component?.options?.dictionaryValue?["aspectRatio"]?.doubleValue {
self.aspectRatio = CGFloat(1 / ratio)
}

self.children = block.children
self.contentMode = block.component?.options?["backgroundSize"] == "cover" ? .fill : .fit
self.contentMode =
block.component?.options?.dictionaryValue?["backgroundSize"]?.stringValue == "cover"
? .fill : .fit
self.fitContent =
(block.component?.options?["fitContent"].boolValue ?? false)
(block.component?.options?.dictionaryValue?["fitContent"]?.boolValue ?? false)
&& !(block.children?.isEmpty ?? true)

}
Expand Down
8 changes: 4 additions & 4 deletions Sources/BuilderIO/Components/BuilderSection.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import Foundation
import SwiftUI
import SwiftyJSON

struct BuilderSection: BuilderViewProtocol {

Expand All @@ -13,10 +12,11 @@ struct BuilderSection: BuilderViewProtocol {
init(block: BuilderBlockModel) {
self.block = block
self.children = block.children
self.lazyLoad = block.component?.options?["lazyLoad"].bool ?? false
self.lazyLoad = block.component?.options?.dictionaryValue?["lazyLoad"]?.boolValue ?? false
self.maxWidth =
block.component?.options?["maxWidth"] != nil
? CGFloat(block.component?.options?["maxWidth"].float ?? .infinity) : nil
block.component?.options?.dictionaryValue?["maxWidth"] != nil
? CGFloat(block.component?.options?.dictionaryValue?["maxWidth"]?.doubleValue ?? .infinity)
: nil
}

var body: some View {
Expand Down
2 changes: 1 addition & 1 deletion Sources/BuilderIO/Components/BuilderText.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ struct BuilderText: BuilderViewProtocol {

init(block: BuilderBlockModel) {
self.block = block
self.text = block.component?.options?["text"].string ?? ""
self.text = block.component?.options?.dictionaryValue?["text"]?.stringValue ?? ""
self.responsiveStyles = getFinalStyle(responsiveStyles: block.responsiveStyles)
}

Expand Down
12 changes: 7 additions & 5 deletions Sources/BuilderIO/EventActions/BuilderActionHandler.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import SwiftUI
import SwiftyJSON

public typealias BuilderActionHandler = (BuilderAction) -> Void

Expand All @@ -16,7 +15,8 @@ public class BuilderActionManager: ObservableObject {

public func handleButtonPress(builderAction: BuilderAction) {

var url: String? = builderAction.options?["link"].string ?? builderAction.linkURL
var url: String? =
builderAction.options?.dictionaryValue?["link"]?.stringValue ?? builderAction.linkURL

//<CUSTOM_SCHEME>://<MODEL_NAME>/<PAGE_URL>?<OPTIONAL_PARAMETERS>
//"builderio://page/my-awesome-page
Expand Down Expand Up @@ -61,10 +61,12 @@ public class BuilderActionManager: ObservableObject {
public class BuilderAction {
let componentId: String
let linkURL: String?
let options: JSON?
let eventActions: JSON?
let options: AnyCodable?
let eventActions: AnyCodable?

public init(componentId: String, options: JSON?, eventActions: JSON?, linkURL: String? = nil) {
public init(
componentId: String, options: AnyCodable?, eventActions: AnyCodable?, linkURL: String? = nil
) {
self.componentId = componentId
self.options = options
self.eventActions = eventActions
Expand Down
101 changes: 101 additions & 0 deletions Sources/BuilderIO/Schemas/AnyCodable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import Foundation

// --- AnyCodable Type Definition ---
// This enum allows you to represent any valid JSON value and make it Codable.
public enum AnyCodable: Codable {
case int(Int)
case double(Double)
case string(String)
case bool(Bool)
case array([AnyCodable]) // Can contain other AnyCodable values
case dictionary([String: AnyCodable]) // Can contain other AnyCodable values
case null

// MARK: - Decodable
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()

if let value = try? container.decode(Int.self) {
self = .int(value)
} else if let value = try? container.decode(Double.self) {
self = .double(value)
} else if let value = try? container.decode(String.self) {
self = .string(value)
} else if let value = try? container.decode(Bool.self) {
self = .bool(value)
} else if let value = try? container.decode([AnyCodable].self) {
self = .array(value)
} else if let value = try? container.decode([String: AnyCodable].self) {
self = .dictionary(value)
} else if container.decodeNil() {
self = .null
} else {
throw DecodingError.dataCorrupted(
DecodingError.Context(
codingPath: decoder.codingPath,
debugDescription: "AnyCodable: Unknown type or malformed JSON value.")
)
}
}

// MARK: - Encodable
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .int(let value):
try container.encode(value)
case .double(let value):
try container.encode(value)
case .string(let value):
try container.encode(value)
case .bool(let value):
try container.encode(value)
case .array(let value):
try container.encode(value)
case .dictionary(let value):
try container.encode(value)
case .null:
try container.encodeNil()
}
}

// MARK: - Convenience for accessing values (optional but highly recommended)
// Add these so you can easily access specific types without a full switch
public var intValue: Int? {
if case .int(let val) = self { return val }
if case .string(let str) = self, let val = Int(str) { return val }
return nil
}

public var doubleValue: Double? {
if case .double(let val) = self { return val }
if case .int(let val) = self { return Double(val) }
if case .string(let str) = self, let val = Double(str) { return val }
return nil
}

public var stringValue: String? {
if case .string(let val) = self { return val }
if case .int(let val) = self { return String(val) }
if case .double(let val) = self { return String(val) }
if case .bool(let val) = self { return String(val) }
return nil
}

public var boolValue: Bool? {
if case .bool(let val) = self { return val }
if case .int(let val) = self { return val != 0 } // 0 = false, non-zero = true
if case .string(let str) = self { return Bool(str) } // "true", "false"
return nil
}

public var arrayValue: [AnyCodable]? {
if case .array(let val) = self { return val }
return nil
}

public var dictionaryValue: [String: AnyCodable]? {
if case .dictionary(let val) = self { return val }
return nil
}
}
11 changes: 6 additions & 5 deletions Sources/BuilderIO/Schemas/BuilderBlockModel.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import SwiftyJSON
import Foundation // For Codable, Data, etc.

// Schema for Builder blocks
public struct BuilderBlockModel: Codable, Identifiable {
Expand All @@ -8,15 +8,16 @@ public struct BuilderBlockModel: Codable, Identifiable {
public var children: [BuilderBlockModel]? = []
public var component: BuilderBlockComponent? = nil
public var responsiveStyles: BuilderBlockResponsiveStyles? = BuilderBlockResponsiveStyles() // for inner style of the component
public var actions: JSON? = [:]
public var code: JSON? = [:]
public var meta: JSON? = [:]
public var actions: AnyCodable? = nil // Replaced JSON? with AnyCodable?, default to nil
public var code: AnyCodable? = nil // Replaced JSON? with AnyCodable?, default to nil
public var meta: AnyCodable? = nil // Replaced JSON? with AnyCodable?, default to nil
public var linkUrl: String? = nil

}

public struct BuilderBlockComponent: Codable {
public var name: String
public var options: JSON? = [:]
public var options: AnyCodable? = nil // Replaced JSON? with AnyCodable?, default to nil
}

public struct BuilderBlockResponsiveStyles: Codable {
Expand Down
1 change: 0 additions & 1 deletion Tests/BuilderIOTests/BuilderIOPageViewTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -74,5 +74,4 @@ class BuilderIOPageViewTests: XCTestCase {

return hostingController
}

}
Loading
Loading