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
2,146 changes: 1,039 additions & 1,107 deletions bun.lock

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions example/src/NitroImageTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ export function NitroImageTab() {
data={imageURLs}
renderItem={({ item: url }) => (
<NitroImage
image={{ url: url }}
image={{ url: url }}
style={styles.image}
resizeMode="cover" />
resizeMode="cover" />
)}
/>
</View>
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-native-nitro-image-monorepo",
"packageManager": "bun@1.3.6",
"packageManager": "bun@1.3.9",
"private": true,
"version": "0.10.2",
"repository": "https://github.com/mrousavy/react-native-nitro-image.git",
Expand Down
6 changes: 3 additions & 3 deletions packages/react-native-nitro-image/ios/CustomImageView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ internal class CustomImageView: UIImageView {
fatalError("init(coder:) has not been implemented")
}

override func willMove(toSuperview newSuperview: UIView?) {
super.willMove(toSuperview: newSuperview)
onVisibilityChanged(isVisible: newSuperview != nil)
override func willMove(toWindow newWindow: UIWindow?) {
super.willMove(toWindow: newWindow)
onVisibilityChanged(isVisible: newWindow != nil)
}
private func onVisibilityChanged(isVisible: Bool) {
if isVisible {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//
// DispatchQueue+runOnMain.swift
// react-native-nitro-image
//
// Created by Marc Rousavy on 10.02.26.
//

import Foundation

extension DispatchQueue {
static func runOnMain(_ block: @escaping () -> Void) {
if Thread.isMainThread {
block()
} else {
DispatchQueue.main.async(execute: block)
}
}
}
88 changes: 52 additions & 36 deletions packages/react-native-nitro-image/ios/HybridImageLoader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,59 +11,75 @@ class HybridImageLoader: HybridImageLoaderSpec {
typealias LoadFunc = () throws -> Promise<any HybridImageSpec>
typealias RequestFunc = (_ for: any HybridNitroImageViewSpec) throws -> Void
typealias DropFunc = (_ for: any HybridNitroImageViewSpec) throws -> Void

private let load: LoadFunc
private let requestImage: RequestFunc
private let dropImage: DropFunc

init(load: @escaping LoadFunc) {
private let requestImage: RequestFunc?
private let dropImage: DropFunc?
private let allowCaching: Bool
private var cachedResult: (any HybridImageSpec)? = nil

init(load: @escaping LoadFunc, allowCaching: Bool = true) {
self.load = load
self.requestImage = Self.defaultRequestFunc(forLoadFunc: load)
self.dropImage = Self.defaultDropFunc()
self.requestImage = nil
self.dropImage = nil
self.allowCaching = allowCaching
}
init(load: @escaping LoadFunc,
requestImage: @escaping RequestFunc,
dropImage: @escaping DropFunc) {
dropImage: @escaping DropFunc,
allowCaching: Bool = true) {
self.load = load
self.requestImage = requestImage
self.dropImage = dropImage
self.allowCaching = allowCaching
}

func loadImage() throws -> Promise<any HybridImageSpec> {
return try load()
}

func requestImage(forView view: any HybridNitroImageViewSpec) throws {
return try requestImage(view)

func dispose() {
self.cachedResult = nil
}

func dropImage(forView view: any HybridNitroImageViewSpec) throws {
return try dropImage(view)

func loadImage() throws -> Promise<any HybridImageSpec> {
if allowCaching {
// We can cache the last loaded image in state, so future requests receive it instantly
if let cachedResult {
return .resolved(withResult: cachedResult)
}
return try load()
.then { [weak self] image in
guard let self else { return }
self.cachedResult = image
}
} else {
// We need to reload the Image each time.
return try load()
}
}
}

fileprivate extension HybridImageLoader {
static func defaultRequestFunc(forLoadFunc loadFunc: @escaping LoadFunc) -> RequestFunc {
return { view in
func requestImage(forView view: any HybridNitroImageViewSpec) throws {
if let requestImage {
// Custom requestImage(...) func supplied
return try requestImage(view)
} else {
// Default: Load & set image.
guard let view = view as? NativeImageView else { return }
let promise = try loadFunc()
promise.then { image in
guard let image = image as? NativeImage else { return }
Task { @MainActor in
view.imageView.image = image.uiImage
try loadImage()
.then { image in
guard let image = image as? NativeImage else { return }
DispatchQueue.runOnMain {
view.imageView.image = image.uiImage
}
}
}
promise.catch { _ in
Task { @MainActor in
view.imageView.image = nil
}
}
}
}
static func defaultDropFunc() -> DropFunc {
return { view in

func dropImage(forView view: any HybridNitroImageViewSpec) throws {
if let dropImage {
// Custom dropImage(...) func supplied
return try dropImage(view)
} else {
// Default: Set image to nil
guard let view = view as? NativeImageView else { return }
Task { @MainActor in
DispatchQueue.runOnMain {
view.imageView.image = nil
}
}
Expand Down
4 changes: 2 additions & 2 deletions packages/react-native-nitro-image/ios/HybridImageView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@ class HybridImageView: HybridNitroImageViewSpec {

var resizeMode: ResizeMode? {
didSet {
Task { @MainActor in
DispatchQueue.runOnMain {
self.updateResizeMode()
}
}
}
var image: (Variant__any_HybridImageSpec___any_HybridImageLoaderSpec_)? = nil {
didSet {
Task { @MainActor in
DispatchQueue.runOnMain {
self.updateImage()
}
}
Expand Down
3 changes: 2 additions & 1 deletion packages/react-native-nitro-image/src/OptionalWebLoader.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// biome-ignore lint/suspicious/noTsIgnore: Type Compilation is a race-condition
// @ts-ignore
type WebImagesType = typeof import("react-native-nitro-web-image")["WebImages"];
type OptionalWebImagesType = Pick<WebImagesType, 'createWebImageLoader' | 'loadFromURLAsync'>

let createWebImageLoader: WebImagesType["createWebImageLoader"] = () => {
throw new Error(
Expand All @@ -26,4 +27,4 @@ try {
// react-native-nitro-web-image is not installed, so only local images are supported.
}

export const OptionalWebImages = { createWebImageLoader, loadFromURLAsync };
export const OptionalWebImages: OptionalWebImagesType = { createWebImageLoader, loadFromURLAsync };
Loading