Skip to content
Open
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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
8 changes: 4 additions & 4 deletions .github/workflows/ci-master-only.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ on:

jobs:
cocoapods-lint:
env:
DEVELOPER_DIR: /Applications/Xcode_16.4.0.app/Contents/Developer
env:
DEVELOPER_DIR: /Applications/Xcode_26.0.1.app/Contents/Developer
name: Verify that podspec lints
runs-on: macos-latest
runs-on: macos-15
steps:
- name: Checkout the Git repository
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Run build.sh with cocoapods-lint mode
run: ./build.sh cocoapods-lint
6 changes: 3 additions & 3 deletions .github/workflows/ci-pull-requests-only.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ on:
jobs:
buildsh:
env:
DEVELOPER_DIR: /Applications/Xcode_16.4.0.app/Contents/Developer
DEVELOPER_DIR: /Applications/Xcode_26.0.1.app/Contents/Developer
strategy:
matrix:
mode: [cocoapods-lint-default-subspecs, cocoapods-lint-other-subspecs]
Expand All @@ -18,9 +18,9 @@ jobs:
- mode: cocoapods-lint-other-subspecs
name: Verify that other subspecs lint
name: ${{ matrix.name }}
runs-on: macos-latest
runs-on: macos-15
steps:
- name: Checkout the Git repository
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Run build script
run: ./build.sh ${{ matrix.mode }}
16 changes: 12 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ on: [push, pull_request]
jobs:
buildsh:
env:
DEVELOPER_DIR: /Applications/Xcode_16.4.0.app/Contents/Developer
DEVELOPER_DIR: /Applications/Xcode_26.0.1.app/Contents/Developer
strategy:
matrix:
mode: [tests, framework, life-without-cocoapods, carthage, examples-pt1, examples-pt2, examples-pt3, examples-pt4]
mode: [tests, framework, life-without-cocoapods, carthage, spm, spm-texture-basic, spm-texture-iglistkit, spm-app-iglistkit, examples-pt1, examples-pt2, examples-pt3, examples-pt4]
include:
- mode: tests
name: Build and run tests
Expand All @@ -18,6 +18,14 @@ jobs:
name: Build Texture as a static library
- mode: carthage
name: Verify that Carthage works
- mode: spm
name: Verify that Swift Package Manager works
- mode: spm-texture-basic
name: Test SPM basic integration (AsyncDisplayKit)
- mode: spm-texture-iglistkit
name: Test SPM with IGListKit extensions
- mode: spm-app-iglistkit
name: Test SPM iOS app with IGListKit
- mode: examples-pt1
name: Build examples (examples-pt1)
- mode: examples-pt2
Expand All @@ -27,9 +35,9 @@ jobs:
- mode: examples-pt4
name: Build examples (examples-pt4)
name: ${{ matrix.name }}
runs-on: macos-latest
runs-on: macos-15
steps:
- name: Checkout the Git repository
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Run build script
run: ./build.sh ${{ matrix.mode }}
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,8 @@ playground.xcworkspace
# Carthage
Carthage/Checkouts
Carthage/Build

# Swift Package Manager
.build/
.swiftpm/
Package.resolved
14 changes: 14 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,20 @@ Before submitting a pull request, please make sure the following is done…
5. Ensure tests pass CI on GitHub for your Pull Request.
6. If you haven't already, sign the CLA.

## Swift Package Manager (SPM) Contributions

If your changes add, remove, or move source files in the `Source/` directory, you **must** regenerate the SPM symlink structure:

```bash
# Regenerate SPM layout
swift scripts/generate_spm_sources_layout.swift

# Commit the generated changes
git add spm/Sources
```

**Important:** Always include the generated `spm/Sources` directory changes in your pull request. The CI will fail if the SPM layout is out of sync with the source files. Our build script automatically verifies this by cleaning and regenerating the layout during testing.

**Copyright Notice for files**
Copy and paste this to the top of your new file(s):
```objc
Expand Down
135 changes: 135 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
// swift-tools-version: 6.1
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

// MARK: - SPM Source Distribution
//
// This package provides source distribution for Texture with Swift Package Manager.
//
// Features:
// - Core AsyncDisplayKit (all nodes, layout specs, TextNode2)
// - PINRemoteImage integration
// - IGListKit Swift wrapper (TextureIGListKitExtensions)
//
// IGListKit Integration:
// The native Objective-C IGListKit API (IGListAdapter+AsyncDisplayKit, etc.) is disabled
// because it requires conditional compilation flags (AS_IG_LIST_KIT=1) which create
// Objective-C headers that cannot be bridged to Swift. Instead, use the Swift wrapper:
//
// - Import: TextureIGListKitExtensions
// - API: adapter.setCollectionNode(node) instead of adapter.setASDKCollectionNode(node)
// - See Sources/TextureIGListKitExtensions/README.md for complete API mapping
//
// Other Limitations:
// - Video/MapKit/Photos features not accessible (require framework linking)
// - Old TextNode disabled (use modern TextNode2)

let package = Package(
name: "Texture",
platforms: [
.iOS(.v14),
.tvOS(.v14),
.macCatalyst(.v13)
],
products: [
.library(
name: "AsyncDisplayKit",
targets: ["AsyncDisplayKit"]
),
.library(
name: "TextureIGListKitExtensions",
targets: ["TextureIGListKitExtensions"]
)
],
dependencies: [
.package(url: "https://github.com/pinterest/PINRemoteImage.git", from: "3.0.4"),
.package(url: "https://github.com/Instagram/IGListKit", from: "5.0.0")
],
targets: [
.target(
name: "AsyncDisplayKit",
dependencies: [
"PINRemoteImage"
],
path: "spm/Sources/AsyncDisplayKit",
publicHeadersPath: "include",
cSettings: [
// Always available features
.define("AS_PIN_REMOTE_IMAGE", to: "1"),

// Disable old TextNode by default for SPM
.define("AS_ENABLE_TEXTNODE", to: "0"),

// IGListKit: Disabled for SPM (use TextureIGListKitExtensions instead)
.define("AS_IG_LIST_KIT", to: "0"),
.define("AS_IG_LIST_DIFF_KIT", to: "0"),

// Disabled features
.define("AS_USE_VIDEO", to: "0"), // Not accessible from Swift via SPM
.define("AS_USE_MAPKIT", to: "0"), // Not accessible from Swift via SPM
.define("AS_USE_PHOTOS", to: "0"), // Partially accessible from Swift via SPM
.define("AS_USE_ASSETS_LIBRARY", to: "0"), // Deprecated iOS 9.0, use Photos framework

// Always disabled for SPM
.define("IG_LIST_COLLECTION_VIEW", to: "0"),

// Header search paths
.headerSearchPath("."),
.headerSearchPath("include/AsyncDisplayKit"), // For quoted-style imports
.headerSearchPath("Base"),
.headerSearchPath("Debug"),
.headerSearchPath("Details"),
.headerSearchPath("Details/Transactions"),
.headerSearchPath("Layout"),
.headerSearchPath("Private"),
.headerSearchPath("Private/Layout"),
.headerSearchPath("TextExperiment/Component"),
.headerSearchPath("TextExperiment/String"),
.headerSearchPath("TextExperiment/Utility"),
.headerSearchPath("TextKit"),
.headerSearchPath("tvOS")
],
cxxSettings: [
.headerSearchPath("."),
.headerSearchPath("include/AsyncDisplayKit"), // For quoted-style imports
.headerSearchPath("Base"),
.headerSearchPath("Debug"),
.headerSearchPath("Details"),
.headerSearchPath("Details/Transactions"),
.headerSearchPath("Layout"),
.headerSearchPath("Private"),
.headerSearchPath("Private/Layout"),
.headerSearchPath("TextExperiment/Component"),
.headerSearchPath("TextExperiment/String"),
.headerSearchPath("TextExperiment/Utility"),
.headerSearchPath("TextKit"),
.headerSearchPath("tvOS")
],
linkerSettings: [
.linkedLibrary("c++")
// Note: Video/MapKit/Photos frameworks not linked by default
// These features are not accessible from Swift via SPM due to conditional compilation
// Use CocoaPods or Carthage if you need these features, or use Objective-C code
]
),
.target(
name: "TextureIGListKitExtensions",
dependencies: [
"AsyncDisplayKit",
.product(name: "IGListKit", package: "IGListKit"),
.product(name: "IGListDiffKit", package: "IGListKit")
],
path: "Sources/TextureIGListKitExtensions",
swiftSettings: [
.enableExperimentalFeature("AccessLevelOnImport"),
// Use Swift 5 mode because this is a wrapper around Objective-C code
// that lacks Swift Concurrency annotations. When AsyncDisplayKit adds
// proper @MainActor annotations, we can migrate to Swift 6 mode.
.swiftLanguageMode(.v5)
]
)
],
cLanguageStandard: .c11,
cxxLanguageStandard: .cxx20
)
120 changes: 119 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,129 @@

[![Version](https://img.shields.io/cocoapods/v/Texture.svg)](http://cocoapods.org/pods/Texture)
[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-59C939.svg?style=flat)](https://github.com/Carthage/Carthage)
[![Swift Package Manager](https://img.shields.io/badge/Swift%20Package%20Manager-compatible-brightgreen.svg)](https://swift.org/package-manager/)
[![License](https://img.shields.io/cocoapods/l/Texture.svg)](https://github.com/texturegroup/texture/blob/master/LICENSE)

## Installation

Texture is available via CocoaPods or Carthage. See our [Installation](http://texturegroup.org/docs/installation.html) guide for instructions.
Texture is available via CocoaPods, Carthage, or Swift Package Manager. See our [Installation](http://texturegroup.org/docs/installation.html) guide for instructions.

### Swift Package Manager

Texture supports Swift Package Manager with Package Traits for modular feature integration.

#### Basic Usage (AsyncDisplayKit only)

Most users just need the core AsyncDisplayKit functionality:

```swift
// In your Package.swift
dependencies: [
.package(url: "https://github.com/TextureGroup/Texture.git", from: "3.3.0")
],
targets: [
.target(
name: "YourApp",
dependencies: [
.product(name: "AsyncDisplayKit", package: "Texture")
]
)
]
```

**Default Features (included automatically):**
- Core AsyncDisplayKit (ASDisplayNode, ASImageNode, ASTextNode2, ASButtonNode, etc.)
- PINRemoteImage integration (ASPINRemoteImageDownloader)
- Collection views (ASCollectionNode, ASTableNode)
- Layout specs (ASStackLayoutSpec, ASInsetLayoutSpec, etc.)
- TextNode2 (modern text rendering, replaces legacy TextNode)

**Optional Features (enable via traits):**
- IGListKit integration (advanced collection views with modern Swift API)

**⚠️ SPM Limitations:**
Video (ASVideoNode), MapKit (ASMapNode), and Photos features are **not available** via Swift Package Manager due to technical limitations. These Objective-C classes are wrapped in conditional compilation directives (`#if AS_USE_VIDEO`) which prevents them from being exported in the Swift module interface.

**If you need Video/MapKit/Photos features:**
- Use **CocoaPods** or **Carthage** (full feature support)
- Or use these features from **Objective-C code** (.m files)

**Future directions:** We're exploring solutions like Swift wrapper modules (TextureVideoExtensions, TextureMapKitExtensions) to provide Swift API for these features via SPM.

#### Advanced Usage: IGListKit Integration

For advanced collection view support with IGListKit, you need **both steps**:

1. **Enable the IGListKit trait** on the package dependency
2. **Add the TextureIGListKitExtensions product** to your target

```swift
// In your Package.swift
dependencies: [
.package(
url: "https://github.com/TextureGroup/Texture.git",
from: "3.3.0",
traits: [.init(name: "IGListKit")] // Step 1: Enable trait
)
],
targets: [
.target(
name: "YourApp",
dependencies: [
.product(name: "AsyncDisplayKit", package: "Texture"),
.product(name: "TextureIGListKitExtensions", package: "Texture") // Step 2: Add product
]
)
]
```

**Why both steps?** Due to Swift Package Manager limitations, traits apply to the entire package, not individual products. This means you must explicitly enable the IGListKit trait AND add the product dependency. See [Issue #8350](https://github.com/swiftlang/swift-package-manager/issues/8350) for details.

**⚠️ Important Notes:**
- **SPM uses IGListKit 5.0+** (breaking changes from 4.x used in CocoaPods/Carthage)
- **Not a drop-in replacement** - migration and testing required
- **No Carthage/CocoaPods support planned** - we recommend migrating to SPM
- Provides Swift API: `ListAdapter.setCollectionNode(_:)` (replaces Objective-C `setASDKCollectionNode:`)

📖 **[Read the full IGListKit migration guide →](Sources/TextureIGListKitExtensions/README.md)**

#### Migrating from CocoaPods to SPM

If you're migrating from CocoaPods, here's how the subspecs map to SPM features:

| Feature | CocoaPods | SPM | Notes |
|---------|-----------|-----|-------|
| **Core** | `pod 'Texture'` (default) | `.product(name: "AsyncDisplayKit", ...)` | ✅ Always included |
| **PINRemoteImage** | Included by default | Always included | ✅ Same behavior |
| **Video** | Included by default | **Not available** | ❌ SPM limitation (see above) |
| **MapKit** | Included by default | **Not available** | ❌ SPM limitation (see above) |
| **Photos** | Included by default | **Not available** | ❌ SPM limitation (see above) |
| **AssetsLibrary** | Included by default | **Removed** | ❌ Deprecated iOS 9.0, use Photos |
| **IGListKit** | `pod 'Texture/IGListKit'` | Optional trait + product | ⚠️ Uses IGListKit 5.0+ |
| **TextNode2** | `pod 'Texture/TextNode2'` | Enabled by default | ✅ Modern TextNode used |
| **Yoga** | `pod 'Texture/Yoga'` | Not supported | Add as separate dependency |

**Key differences:**
- **TextNode2 is default**: SPM uses the modern TextNode implementation automatically (no legacy TextNode)
- ❌ **Video/MapKit/Photos not available**: Due to Swift Package Manager limitations with conditionally compiled Objective-C classes
- ❌ **AssetsLibrary removed**: Deprecated in iOS 9.0, use Photos framework instead
- ⚠️ **IGListKit version**: SPM uses IGListKit 5.0+ instead of 4.x (breaking changes)
- ℹ️ **Yoga**: Not integrated in SPM - add Yoga as a separate dependency if needed

#### Note for Contributors

When adding or removing source files in the `Source/` directory, you must regenerate the SPM symlink structure:

```bash
# Regenerate SPM layout
swift scripts/generate_spm_sources_layout.swift

# Commit the generated changes
git add spm/Sources
git commit -m "Update SPM layout for new/removed files"
```

**Important:** Always commit the generated `spm/Sources` directory changes along with your source file changes. This ensures SPM users can build the project correctly.

## Performance Gains

Expand Down
4 changes: 2 additions & 2 deletions Source/Private/ASIGListAdapterBasedDataSource.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,6 @@ AS_SUBCLASSING_RESTRICTED

@end

#endif

NS_ASSUME_NONNULL_END

#endif
Loading