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
14 changes: 7 additions & 7 deletions bun.lock

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

8 changes: 4 additions & 4 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ PODS:
- ReactCommon/turbomodule/core
- SocketRocket
- Yoga
- NitroModules (0.32.1):
- NitroModules (0.33.0):
- boost
- DoubleConversion
- fast_float
Expand Down Expand Up @@ -2740,9 +2740,9 @@ SPEC CHECKSUMS:
glog: 5683914934d5b6e4240e497e0f4a3b42d1854183
hermes-engine: e7491a2038f2618c8cd444ed411a6deb350a3742
libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8
NitroImage: 5ac8b2795c7106d95064d07208ded5b5a3287e62
NitroModules: e28ee6e0ad1246a20db02fe73ba994225fb503ff
NitroWebImage: 3421f3c6c9e8a8dcac3bce72faa9e5c53debdbe4
NitroImage: e247502478146ffcb0727d37fbcee63e150188a0
NitroModules: a375db5ca93f5f8a9b8fdd81bf3b8e94900b183d
NitroWebImage: 5e1dafa038c9f31da5e01c1909bc833d727a29d0
RCT-Folly: 59ec0ac1f2f39672a0c6e6cecdd39383b764646f
RCTDeprecation: 0735ab4f6b3ec93a7f98187b5da74d7916e2cf4c
RCTRequired: 8fcc7801bfc433072287b0f24a662e2816e89d0c
Expand Down
2 changes: 1 addition & 1 deletion example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"react-native-fast-image": "^8.6.3",
"react-native-nitro-image": "../packages/react-native-nitro-image",
"react-native-nitro-web-image": "../packages/react-native-nitro-web-image",
"react-native-nitro-modules": "0.32.1",
"react-native-nitro-modules": "0.33.0",
"react-native-safe-area-context": "^5.6.0",
"react-native-screens": "^4.14.1"
},
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ class HybridImageFactory: HybridImageFactorySpec() {
return HybridImage(bitmap)
}
override fun loadFromRawPixelDataAsync(data: RawPixelData, allowGpu: Boolean?): Promise<HybridImageSpec> {
val bufferCopy = data.buffer.copyIfNotOwner()
val bufferCopy = data.buffer.asOwning()
val dataCopy = RawPixelData(bufferCopy, data.width, data.height, data.pixelFormat)
return Promise.async { loadFromRawPixelData(dataCopy, allowGpu) }
}
Expand Down Expand Up @@ -111,13 +111,12 @@ class HybridImageFactory: HybridImageFactorySpec() {
}

override fun loadFromThumbHash(thumbhash: ArrayBuffer): HybridImageSpec {
// copyIfNeeded can be false since we are running this synchronously
val bytes = thumbhash.toByteArray()
return loadFromThumbHash(bytes)
}

override fun loadFromThumbHashAsync(thumbhash: ArrayBuffer): Promise<HybridImageSpec> {
// copyIfNeeded needs to be true since we switch threads
// We need to copy before jumping Threads
val bytes = thumbhash.toByteArray()
return Promise.async { loadFromThumbHash(bytes) }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@ import android.widget.ImageView
import androidx.annotation.Keep
import androidx.core.view.isVisible
import com.facebook.common.internal.DoNotStrip
import com.margelo.nitro.views.RecyclableView
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch

@DoNotStrip
@Keep
class HybridImageView(context: Context): HybridNitroImageViewSpec() {
class HybridImageView(context: Context): HybridNitroImageViewSpec(), RecyclableView {
companion object {
private const val TAG = "HybridImageView"
}
Expand Down Expand Up @@ -48,6 +49,11 @@ class HybridImageView(context: Context): HybridNitroImageViewSpec() {
field = value
}

override fun prepareForRecycle() {
onDisappear()
imageView.setImageBitmap(null)
}

private fun updateResizeMode() {
imageView.scaleType = when (resizeMode) {
ResizeMode.COVER -> ImageView.ScaleType.CENTER_CROP
Expand Down
41 changes: 41 additions & 0 deletions packages/react-native-nitro-image/ios/CustomImageView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
//
// CustomImageView.swift
// react-native-nitro-image
//
// Created by Marc Rousavy on 12.01.26.
//

import Foundation
import UIKit

internal protocol ViewLifecycleDelegate: AnyObject {
func willShow()
func willHide()
}

internal class CustomImageView: UIImageView {
internal weak var delegate: (any ViewLifecycleDelegate)? = nil {
didSet {
onVisibilityChanged(isVisible: self.isVisible)
}
}

init() {
super.init(image: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

override func willMove(toSuperview newSuperview: UIView?) {
super.willMove(toSuperview: newSuperview)
onVisibilityChanged(isVisible: newSuperview != nil)
}
private func onVisibilityChanged(isVisible: Bool) {
if isVisible {
delegate?.willShow()
} else {
delegate?.willHide()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//
// UIView+isVisible.swift
// react-native-nitro-image
//
// Created by Marc Rousavy on 12.01.26.
//

import UIKit

extension UIView {
var isVisible: Bool {
return superview != nil
}
}
77 changes: 24 additions & 53 deletions packages/react-native-nitro-image/ios/HybridImageView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,55 +9,13 @@ import Foundation
import UIKit
import NitroModules

fileprivate protocol ViewLifecycleDelegate: AnyObject {
func willShow()
func willHide()
}

class CustomImageView: UIImageView {
fileprivate weak var delegate: ViewLifecycleDelegate? = nil {
didSet {
onVisibilityChanged(isVisible: superview != nil)
}
}

init() {
super.init(image: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

override func willMove(toSuperview newSuperview: UIView?) {
super.willMove(toSuperview: newSuperview)
onVisibilityChanged(isVisible: newSuperview != nil)
}
private func onVisibilityChanged(isVisible: Bool) {
if isVisible {
delegate?.willShow()
} else {
delegate?.willHide()
}
}
}

fileprivate extension UIView {
var isVisible: Bool {
return superview != nil
}
}

class HybridImageView: HybridNitroImageViewSpec, NativeImageView {
let imageView: UIImageView
let view: UIView
class HybridImageView: HybridNitroImageViewSpec {
let view = CustomImageView()
private var resetImageBeforeLoad = false

override init() {
let imageView = CustomImageView()
self.imageView = imageView
self.view = imageView
super.init()
imageView.delegate = self
view.delegate = self
}

var resizeMode: ResizeMode? {
Expand All @@ -84,13 +42,13 @@ class HybridImageView: HybridNitroImageViewSpec, NativeImageView {
let mode = resizeMode ?? .cover
switch mode {
case .cover:
imageView.contentMode = .scaleAspectFill
view.contentMode = .scaleAspectFill
case .contain:
imageView.contentMode = .scaleAspectFit
view.contentMode = .scaleAspectFit
case .stretch:
imageView.contentMode = .scaleToFill
view.contentMode = .scaleToFill
case .center:
imageView.contentMode = .center
view.contentMode = .center
}
}

Expand All @@ -101,26 +59,31 @@ class HybridImageView: HybridNitroImageViewSpec, NativeImageView {
guard let image = hybridImageSpec as? NativeImage else {
fatalError("Can't set `image` to a type that doesn't conform to `NativeImage`!")
}
imageView.image = image.uiImage
view.image = image.uiImage
case .second:
// Image Loader - trigger a load or drop
didSetImageLoader()
case nil:
// No Image
imageView.image = nil
view.image = nil
}
}

private func didSetImageLoader() {
// An ImageLoader was set - trigger an update (load or drop)
if imageView.isVisible {
if view.isVisible {
willShow()
} else {
willHide()
}
}
}

// Conform to `NativeImageView` so third-party consumers can cast it
extension HybridImageView: NativeImageView {
var imageView: UIImageView { view }
}

// Implementation for "asynchronously" loading Images using ImageLoader
extension HybridImageView: ViewLifecycleDelegate {
private var imageLoader: HybridImageLoaderSpec? {
Expand All @@ -131,7 +94,7 @@ extension HybridImageView: ViewLifecycleDelegate {
func willShow() {
guard let imageLoader else { return }
if resetImageBeforeLoad {
imageView.image = nil
view.image = nil
resetImageBeforeLoad = false
}
try? imageLoader.requestImage(forView: self)
Expand All @@ -143,3 +106,11 @@ extension HybridImageView: ViewLifecycleDelegate {
}
}

// Implementation to allow view recycling
extension HybridImageView: RecyclableView {
func prepareForRecycle() {
willHide()
view.image = nil
}
}

Loading
Loading