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
4 changes: 4 additions & 0 deletions apple/RNCAssetSchemeHandler.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#import <WebKit/WebKit.h>

@interface RNCAssetSchemeHandler : NSObject <WKURLSchemeHandler>
@end
66 changes: 66 additions & 0 deletions apple/RNCAssetSchemeHandler.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#import "RNCAssetSchemeHandler.h"

@implementation RNCAssetSchemeHandler

- (void)webView:(WKWebView *)webView startURLSchemeTask:(id<WKURLSchemeTask>)urlSchemeTask {
NSURL *url = urlSchemeTask.request.URL;
NSString *path = url.path;

// Strip leading slash from path to get the filename
if ([path hasPrefix:@"/"]) {
path = [path substringFromIndex:1];
}

// URL-decode the path (handles brackets and other special chars)
NSString *fileName = [path stringByRemovingPercentEncoding];
if (!fileName || fileName.length == 0) {
NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil];
[urlSchemeTask didFailWithError:error];
return;
}

// Find the file in the app bundle
NSString *bundleResourcePath = [[NSBundle mainBundle] resourcePath];
NSString *fullPath = [bundleResourcePath stringByAppendingPathComponent:fileName];

if (![[NSFileManager defaultManager] fileExistsAtPath:fullPath]) {
NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil];
[urlSchemeTask didFailWithError:error];
return;
}

NSData *data = [NSData dataWithContentsOfFile:fullPath];
if (!data) {
NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorCannotOpenFile userInfo:nil];
[urlSchemeTask didFailWithError:error];
return;
}

// Determine MIME type from extension
NSString *mimeType = @"application/octet-stream";
NSString *ext = [fileName pathExtension];
if ([ext isEqualToString:@"woff2"]) {
mimeType = @"font/woff2";
} else if ([ext isEqualToString:@"ttf"]) {
mimeType = @"font/ttf";
} else if ([ext isEqualToString:@"otf"]) {
mimeType = @"font/otf";
} else if ([ext isEqualToString:@"woff"]) {
mimeType = @"font/woff";
}

NSURLResponse *response = [[NSURLResponse alloc] initWithURL:url
MIMEType:mimeType
expectedContentLength:data.length
textEncodingName:nil];

[urlSchemeTask didReceiveResponse:response];
[urlSchemeTask didReceiveData:data];
[urlSchemeTask didFinish];
}

- (void)webView:(WKWebView *)webView stopURLSchemeTask:(id<WKURLSchemeTask>)urlSchemeTask {
// Requests complete synchronously, nothing to cancel
}

@end
10 changes: 10 additions & 0 deletions apple/RNCWebView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,8 @@ - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &
REMAP_WEBVIEW_PROP(showsHorizontalScrollIndicator)
REMAP_WEBVIEW_PROP(showsVerticalScrollIndicator)
REMAP_WEBVIEW_PROP(keyboardDisplayRequiresUserAction)
REMAP_WEBVIEW_PROP(scrollsToTop)
REMAP_WEBVIEW_PROP(dragInteractionEnabled)

#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 /* __IPHONE_13_0 */
REMAP_WEBVIEW_PROP(automaticallyAdjustContentInsets)
Expand Down Expand Up @@ -551,5 +553,13 @@ - (void)clearHistory {
// android only
}

- (void)setTintColor:(double)red green:(double)green blue:(double)blue alpha:(double)alpha {
UIColor *color = [UIColor colorWithRed:red / 255.0
green:green / 255.0
blue:blue / 255.0
alpha:alpha];
[_view setTintColor:color];
}

@end
#endif
3 changes: 3 additions & 0 deletions apple/RNCWebViewImpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ shouldStartLoadForRequest:(NSMutableDictionary<NSString *, id> *)request
@property (nonatomic, assign) BOOL pullToRefreshEnabled;
@property (nonatomic, assign) BOOL refreshControlLightMode;
@property (nonatomic, assign) BOOL enableApplePay;
@property (nonatomic, assign) BOOL scrollsToTop;
@property (nonatomic, assign) BOOL dragInteractionEnabled;
@property (nonatomic, copy) NSArray<NSDictionary *> * _Nullable menuItems;
@property (nonatomic, copy) NSArray<NSString *> * _Nullable suppressMenuItems;
@property (nonatomic, copy) RCTDirectEventBlock onCustomMenuSelection;
Expand Down Expand Up @@ -149,6 +151,7 @@ shouldStartLoadForRequest:(NSMutableDictionary<NSString *, id> *)request
- (void)stopLoading;
- (void)requestFocus;
- (void)clearCache:(BOOL)includeDiskFiles;
- (void)setTintColor:(UIColor *)tintColor;
#ifdef RCT_NEW_ARCH_ENABLED
- (void)destroyWebView;
#endif
Expand Down
33 changes: 33 additions & 0 deletions apple/RNCWebViewImpl.m
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*/

#import "RNCWebViewImpl.h"
#import "RNCAssetSchemeHandler.h"
#import <React/RCTConvert.h>
#import <React/RCTAutoInsetsProtocol.h>
#import "RNCWKProcessPoolManager.h"
Expand Down Expand Up @@ -181,6 +182,8 @@ - (instancetype)initWithFrame:(CGRect)frame
_injectedJavaScriptBeforeContentLoaded = nil;
_injectedJavaScriptBeforeContentLoadedForMainFrameOnly = YES;
_enableApplePay = NO;
_scrollsToTop = YES;
_dragInteractionEnabled = YES;
#if TARGET_OS_IOS
_savedStatusBarStyle = RCTSharedApplication().statusBarStyle;
_savedStatusBarHidden = RCTSharedApplication().statusBarHidden;
Expand Down Expand Up @@ -509,6 +512,11 @@ - (WKWebViewConfiguration *)setUpWkWebViewConfig
wkWebViewConfig.applicationNameForUserAgent = [NSString stringWithFormat:@"%@ %@", wkWebViewConfig.applicationNameForUserAgent, _applicationNameForUserAgent];
}

// Register custom URL scheme handler to serve app bundle assets (fonts, etc.)
// from WKWebView pages loaded with about:blank origin, which can't access file:// URLs.
RNCAssetSchemeHandler *assetHandler = [[RNCAssetSchemeHandler alloc] init];
[wkWebViewConfig setURLSchemeHandler:assetHandler forURLScheme:@"rw-asset"];
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is iOS 11+ only and I think we have a target of iOS 9? We should probably check if we need to safely escape if we are on an earlier version.


return wkWebViewConfig;
}

Expand Down Expand Up @@ -545,6 +553,7 @@ - (void)didMoveToWindow
}

_webView.scrollView.directionalLockEnabled = _directionalLockEnabled;
_webView.scrollView.scrollsToTop = _scrollsToTop;
#endif // !TARGET_OS_OSX
_webView.allowsLinkPreview = _allowsLinkPreview;
[_webView addObserver:self forKeyPath:@"estimatedProgress" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:nil];
Expand Down Expand Up @@ -740,6 +749,11 @@ - (void)setBackgroundColor:(RCTUIColor *)backgroundColor
#endif // !TARGET_OS_OSX
}

- (void)setTintColor:(UIColor *)tintColor
{
_webView.tintColor = tintColor;
}

#if !TARGET_OS_OSX
- (void)setContentInsetAdjustmentBehavior:(UIScrollViewContentInsetAdjustmentBehavior)behavior
{
Expand Down Expand Up @@ -1106,6 +1120,25 @@ - (void)setIndicatorStyle:(NSString *)indicatorStyle
_webView.scrollView.indicatorStyle = UIScrollViewIndicatorStyleDefault;
}
}

- (void)setScrollsToTop:(BOOL)scrollsToTop
{
_scrollsToTop = scrollsToTop;
_webView.scrollView.scrollsToTop = scrollsToTop;
}

- (void)setDragInteractionEnabled:(BOOL)dragInteractionEnabled
{
_dragInteractionEnabled = dragInteractionEnabled;
if (@available(iOS 11.0, *)) {
for (id interaction in _webView.scrollView.interactions) {
if ([interaction isKindOfClass:[UIDragInteraction class]]) {
((UIDragInteraction *)interaction).enabled = dragInteractionEnabled;
}
}
}
}

#endif // !TARGET_OS_OSX

- (void)postMessage:(NSString *)message
Expand Down
24 changes: 24 additions & 0 deletions apple/RNCWebViewManager.mm
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,14 @@ - (RNCView *)view
view.keyboardDisplayRequiresUserAction = json == nil ? true : [RCTConvert BOOL: json];
}

RCT_CUSTOM_VIEW_PROPERTY(scrollsToTop, BOOL, RNCWebViewImpl) {
view.scrollsToTop = json == nil ? true : [RCTConvert BOOL: json];
}

RCT_CUSTOM_VIEW_PROPERTY(dragInteractionEnabled, BOOL, RNCWebViewImpl) {
view.dragInteractionEnabled = json == nil ? true : [RCTConvert BOOL: json];
}

#if !TARGET_OS_OSX
#define BASE_VIEW_PER_OS() UIView
#else
Expand Down Expand Up @@ -216,4 +224,20 @@ - (RNCView *)view
QUICK_RCT_EXPORT_COMMAND_METHOD_PARAMS(injectJavaScript, script:(NSString *)script, script)
QUICK_RCT_EXPORT_COMMAND_METHOD_PARAMS(clearCache, includeDiskFiles:(BOOL)includeDiskFiles, includeDiskFiles)

RCT_EXPORT_METHOD(setTintColor:(nonnull NSNumber *)reactTag red:(double)red green:(double)green blue:(double)blue alpha:(double)alpha)
{
[self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, BASE_VIEW_PER_OS() *> *viewRegistry) {
RNCWebViewImpl *view = (RNCWebViewImpl *)viewRegistry[reactTag];
if (![view isKindOfClass:[RNCWebViewImpl class]]) {
RCTLogError(@"Invalid view returned from registry, expecting RNCWebView, got: %@", view);
} else {
UIColor *color = [UIColor colorWithRed:red / 255.0
green:green / 255.0
blue:blue / 255.0
alpha:alpha];
[view setTintColor:color];
}
}];
}

@end
6 changes: 6 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ declare class WebView<P = {}> extends Component<WebViewProps & P> {
* Tells this WebView to clear its internal back/forward list.
*/
clearHistory?: () => void;

/**
* (iOS only)
* Sets the tint color (selection color) of the WebView.
*/
setTintColor: (red: number, green: number, blue: number, alpha: number) => void;
}

export {WebView};
Expand Down
12 changes: 12 additions & 0 deletions src/RNCWebViewNativeComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,9 @@ export interface NativeProps extends ViewProps {
pullToRefreshEnabled?: boolean;
refreshControlLightMode?: boolean;
scrollEnabled?: WithDefault<boolean, true>;
scrollsToTop?: WithDefault<boolean, true>;
sharedCookiesEnabled?: boolean;
dragInteractionEnabled?: WithDefault<boolean, true>;
textInteractionEnabled?: WithDefault<boolean, true>;
useSharedProcessPool?: WithDefault<boolean, true>;
onContentProcessDidTerminate?: DirectEventHandler<WebViewNativeEvent>;
Expand Down Expand Up @@ -325,6 +327,15 @@ export interface NativeCommands {
) => void;
clearHistory: (viewRef: React.ElementRef<HostComponent<NativeProps>>) => void;
// !Android Only

// iOS Only (Readwise custom)
setTintColor: (
viewRef: React.ElementRef<HostComponent<NativeProps>>,
red: Double,
green: Double,
blue: Double,
alpha: Double
) => void;
}

export const Commands = codegenNativeCommands<NativeCommands>({
Expand All @@ -340,6 +351,7 @@ export const Commands = codegenNativeCommands<NativeCommands>({
'clearFormData',
'clearCache',
'clearHistory',
'setTintColor',
],
});

Expand Down
3 changes: 3 additions & 0 deletions src/WebView.ios.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,9 @@
clearCache: (includeDiskFiles: boolean) =>
webViewRef.current &&
Commands.clearCache(webViewRef.current, includeDiskFiles),
setTintColor: (red: number, green: number, blue: number, alpha: number) =>

Check failure on line 162 in src/WebView.ios.tsx

View workflow job for this annotation

GitHub Actions / Running tests

Replace `red:·number,·green:·number,·blue:·number,·alpha:·number` with `⏎··········red:·number,⏎··········green:·number,⏎··········blue:·number,⏎··········alpha:·number⏎········`
webViewRef.current &&
Commands.setTintColor(webViewRef.current, red, green, blue, alpha),
}),
[setViewState, webViewRef]
);
Expand Down
19 changes: 18 additions & 1 deletion src/WebViewTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@

type AndroidWebViewCommands = 'clearHistory' | 'clearFormData';

type IOSWebViewCommands = 'setTintColor';

interface RNCWebViewUIManager<Commands extends string> extends UIManagerStatic {
getViewManagerConfig: (name: string) => {
Commands: { [key in Commands]: number };
Expand All @@ -33,7 +35,7 @@
export type RNCWebViewUIManagerAndroid = RNCWebViewUIManager<
WebViewCommands | AndroidWebViewCommands
>;
export type RNCWebViewUIManagerIOS = RNCWebViewUIManager<WebViewCommands>;
export type RNCWebViewUIManagerIOS = RNCWebViewUIManager<WebViewCommands | IOSWebViewCommands>;

Check failure on line 38 in src/WebViewTypes.ts

View workflow job for this annotation

GitHub Actions / Running tests

Replace `WebViewCommands·|·IOSWebViewCommands` with `⏎··WebViewCommands·|·IOSWebViewCommands⏎`
export type RNCWebViewUIManagerMacOS = RNCWebViewUIManager<WebViewCommands>;
export type RNCWebViewUIManagerWindows = RNCWebViewUIManager<WebViewCommands>;

Expand Down Expand Up @@ -777,6 +779,21 @@
* @platform ios
*/
fraudulentWebsiteWarningEnabled?: boolean;

/**
* A Boolean value that controls whether the web view scrolls to the top of
* the content when the user taps the status bar.
* The default value is `true`.
* @platform ios
*/
scrollsToTop?: boolean;

/**
* A Boolean value that determines whether drag interactions are enabled
* on the web view. The default value is `true`.
* @platform ios
*/
dragInteractionEnabled?: boolean;
}

export interface MacOSWebViewProps extends WebViewSharedProps {
Expand Down
Loading