Skip to content

Commit

Permalink
feat(android,ios,js): add getBoundingClientRect method (Tencent#2651)
Browse files Browse the repository at this point in the history
* feat(android): add `getBoundingClientRect` api [WIP]

* feat(vue,react): add getBoundingClientRect api

* feat(android): add `getBoundingClientRect` api

* feat(ios): add getBoundingClientRect method

* docs(homepage): add getBoundingClientRect doc

* fix(demo): rm redundant file (Tencent#4)

* docs(homepage): add getBoundingClientRect doc

* fix(demo): rm redundant file

* fix(demo): rm redundant file

* refactor(vue-next-demo): rm unused code

* feat(android): add `getBoundingClientRect` api

Co-authored-by: iPel <pel20121221@gmail.com>
Co-authored-by: zoomchan-cxj <zoom_chan@163.com>
Co-authored-by: siguangli <siguangli@qq.com>
  • Loading branch information
4 people authored and pba-cra committed Feb 1, 2023
1 parent 1c71440 commit 8dadbaf
Show file tree
Hide file tree
Showing 19 changed files with 111 additions and 103 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ public void getBoundingClientRect(int id, JSObject options, Promise promise) {
if (domManager == null) {
JSObject result = new JSObject();
result.set(RenderNode.KEY_ERR_MSG, "DomManager is null");
promise.resolve(result);
promise.reject(result);
return;
}
domManager.measureInWindow(id, options, promise);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,7 @@ public void removeViewFromRegistry(int id) {
public void measureInWindow(int id, Promise promise) {
View v = mControllerRegistry.getView(id);
if (v == null) {
promise.resolve("this view is null");
promise.reject("this view is null");
} else {
int[] outputBuffer = new int[4];
int statusBarHeight;
Expand All @@ -405,7 +405,7 @@ public void measureInWindow(int id, Promise promise) {
outputBuffer[2] = v.getWidth();
outputBuffer[3] = v.getHeight();
} catch (Throwable e) {
promise.resolve("exception" + e.getMessage());
promise.reject("exception" + e.getMessage());
e.printStackTrace();
return;
}
Expand Down Expand Up @@ -438,7 +438,7 @@ public void getBoundingClientRect(int id, HippyRootView rootView, boolean relToC
if (v == null) {
JSObject result = new JSObject();
result.set(RenderNode.KEY_ERR_MSG, "this view is null");
promise.resolve(result);
promise.reject(result);
return;
}
int x;
Expand All @@ -450,7 +450,7 @@ public void getBoundingClientRect(int id, HippyRootView rootView, boolean relToC
if (rootView == null) {
JSObject result = new JSObject();
result.set(RenderNode.KEY_ERR_MSG, "container is null");
promise.resolve(result);
promise.reject(result);
return;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -255,11 +255,11 @@ public void measureInWindow(int id, JSObject options, Promise promise) {
RenderNode renderNode = mNodes.get(id);
if (renderNode == null) {
if (options.get(RenderNode.KEY_COMPATIBLE) == Boolean.TRUE) {
promise.resolve("this view is null");
promise.reject("this node is null");
} else {
JSObject result = new JSObject();
result.set(RenderNode.KEY_ERR_MSG, "this node is null");
promise.resolve(result);
promise.reject(result);
}
} else {
renderNode.measureInWindow(options, promise);
Expand Down
2 changes: 1 addition & 1 deletion docs/en-us/hippy-react/modules.md
Original file line number Diff line number Diff line change
Expand Up @@ -481,5 +481,5 @@ Measure the size and position of a component within the scope of the App Contain
`(instance: ref, options: { relToContainer: boolean }) => Promise<DOMRect: { x: number, y: number, width: number, height: number, bottom: number, right: number, left: number, top: number }>`

> - instance: reference of the element of component.
> - options: optional,`relToContainer` indicates whether to be measured relative to the App Container(RootView), default is `false`, meaning relative to App Window(Screen). When measured relative to the App Container(RootView), status bar is included in `iOS`, but `Android` not.
> - options: optional,`relToContainer` indicates whether to be measured relative to the App Container(RootView), default is `false`, meaning relative to App Window(Screen).
> - DOMRect: same with [MDN](https://developer.mozilla.org/zh-CN/docs/Web/API/Element/getBoundingClientRect) introduction, which can get the size and position of a component. If something goes wrong or [the node is optimized (Android only)](style/layout?id=collapsable), `Promise.reject` error will be thrown.
2 changes: 1 addition & 1 deletion docs/en-us/hippy-vue/vue-native.md
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,7 @@ Measure the size and position of a component within the scope of the App Contain
`(instance: ref, options: { relToContainer: boolean }) => Promise<DOMRect: { x: number, y: number, width: number, height: number, bottom: number, right: number, left: number, top: number }>`

> * instance: reference of the element of component.
> * options: optional,`relToContainer` indicates whether to be measured relative to the App Container(RootView). Default is `false`, meaning relative to App Window(Screen). When measured relative to the App Container(RootView), status bar is included in `iOS`, but `Android` not.
> * options: optional,`relToContainer` indicates whether to be measured relative to the App Container(RootView). Default is `false`, meaning relative to App Window(Screen).
> * DOMRect: same with [MDN](https://developer.mozilla.org/zh-CN/docs/Web/API/Element/getBoundingClientRect) introduction, which can get the size and position of a component. If something goes wrong or [the node is optimized (Android only)](style/layout?id=collapsable), `Promise.reject` error will be thrown.
---
Expand Down
2 changes: 1 addition & 1 deletion docs/hippy-react/modules.md
Original file line number Diff line number Diff line change
Expand Up @@ -481,5 +481,5 @@ AsyncStorage 是一个简单的、异步的、持久化的 Key-Value 存储系
`(instance: ref, options: { relToContainer: boolean }) => Promise<DOMRect: { x: number, y: number, width: number, height: number, bottom: number, right: number, left: number, top: number }>`

> - instance: 元素或组件的引用 Ref。
> - options: 可选参数,`relToContainer` 表示是否相对宿主容器(RootView)进行测量,默认 `false` 相对 App 窗口或屏幕进行测量。当对宿主容器(RootView)进行测量时,`iOS` 包含顶部状态栏高度,`Android` 不包含。
> - options: 可选参数,`relToContainer` 表示是否相对宿主容器(RootView)进行测量,默认 `false` 相对 App 窗口或屏幕进行测量。
> - DOMRect: 与 [MDN](https://developer.mozilla.org/zh-CN/docs/Web/API/Element/getBoundingClientRect) 一致的返回参数, 可以获取元素相应的位置信息和尺寸,如果出错或者 [节点被优化(仅在Android)](style/layout?id=collapsable),会触发 `Promise.reject`
2 changes: 1 addition & 1 deletion docs/hippy-vue/vue-native.md
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,7 @@ console.log(Vue.Native.getElemCss(this.demon1Point)) // => { height: 80, left: 0
`(instance: ref, options: { relToContainer: boolean }) => Promise<DOMRect: { x: number, y: number, width: number, height: number, bottom: number, right: number, left: number, top: number }>`

> * instance: 元素或组件的引用 Ref。
> * options: 可选参数,`relToContainer` 表示是否相对宿主容器(RootView)进行测量,默认 `false` 相对 App 窗口或屏幕进行测量。当对宿主容器(RootView)进行测量时,`iOS` 包含顶部状态栏高度,`Android` 不包含。
> * options: 可选参数,`relToContainer` 表示是否相对宿主容器(RootView)进行测量,默认 `false` 相对 App 窗口或屏幕进行测量。
> * DOMRect: 与 [MDN](https://developer.mozilla.org/zh-CN/docs/Web/API/Element/getBoundingClientRect) 一致的返回参数, 可以获取元素相应的位置信息和尺寸,如果出错或者 [节点被优化(仅在Android)](style/layout?id=collapsable),会触发 `Promise.reject`
---
Expand Down
2 changes: 1 addition & 1 deletion examples/android-demo/res/index.android.js

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions examples/android-demo/res/vendor.android.js

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -388,15 +388,11 @@ export default {
},
methods: {
async getBoundingClientRect(relToContainer = false) {
try {
const rect = await Vue.Native.getBoundingClientRect(this.$refs.rect, { relToContainer });
if (!relToContainer) {
this.rect1 = `${JSON.stringify(rect)}`;
} else {
this.rect2 = `${JSON.stringify(rect)}`;
}
} catch (err) {
console.error('getBoundingClientRect error', err);
const rect = await Vue.Native.getBoundingClientRect(this.$refs.rect, { relToContainer });
if (!relToContainer) {
this.rect1 = `${JSON.stringify(rect)}`;
} else {
this.rect2 = `${JSON.stringify(rect)}`;
}
},
triggerAppEvent() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -434,15 +434,11 @@ export default defineComponent({
};
const getBoundingClientRect = async (relToContainer = false) => {
try {
const rect = await Native.getBoundingClientRect(rectRef.value as HippyNode, { relToContainer });
if (!relToContainer) {
rect1.value = `${JSON.stringify(rect)}`;
} else {
rect2.value = `${JSON.stringify(rect)}`;
}
} catch (err) {
console.error('getBoundingClientRect error', err);
const rect = await Native.getBoundingClientRect(rectRef.value as HippyNode, { relToContainer });
if (!relToContainer) {
rect1.value = `${JSON.stringify(rect)}`;
} else {
rect2.value = `${JSON.stringify(rect)}`;
}
};
Expand Down
4 changes: 2 additions & 2 deletions examples/ios-demo/res/index.ios.js

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions examples/ios-demo/res/vendor.ios.js

Large diffs are not rendered by default.

53 changes: 17 additions & 36 deletions ios/sdk/module/uimanager/HippyUIManager.mm
Original file line number Diff line number Diff line change
Expand Up @@ -1256,6 +1256,7 @@ - (void)setNeedsLayout:(NSNumber *)hippyTag {

#pragma mark - Measure Functions

// clang-format off
HIPPY_EXPORT_METHOD(measure:(nonnull NSNumber *)hippyTag
callback:(HippyResponseSenderBlock)callback) {
[self addUIBlock:^(__unused HippyUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
Expand Down Expand Up @@ -1290,52 +1291,39 @@ - (void)setNeedsLayout:(NSNumber *)hippyTag {
]);
}];
}
// clang-format on

static NSString * const HippyUIManagerGetBoundingRelToContainerKey = @"relToContainer";
static NSString * const HippyUIManagerGetBoundingErrMsgrKey = @"errMsg";
HIPPY_EXPORT_METHOD(getBoundingClientRect:(nonnull NSNumber *)hippyTag
options:(nullable NSDictionary *)options
callback:(HippyResponseSenderBlock)callback ) {
if (options && [[options objectForKey:HippyUIManagerGetBoundingRelToContainerKey] boolValue]) {
[self measureInWindow:hippyTag withErrMsg:YES callback:callback];
[self measureInWindow:hippyTag callback:callback];
} else {
[self measureInAppWindow:hippyTag withErrMsg:YES callback:callback];
[self measureInAppWindow:hippyTag callback:callback];
}
}

// clang-format off
HIPPY_EXPORT_METHOD(measureInWindow:(nonnull NSNumber *)hippyTag
callback:(HippyResponseSenderBlock)callback) {
// keep the same as the old version, no errMsg return
[self measureInWindow:hippyTag withErrMsg:NO callback:callback];
}

- (void)measureInWindow:(nonnull NSNumber *)hippyTag
withErrMsg:(BOOL)withErrMsg
callback:(HippyResponseSenderBlock)callback {
callback:(HippyResponseSenderBlock)callback) {
[self addUIBlock:^(__unused HippyUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
UIView *view = viewRegistry[hippyTag];
if (!view) {
// this view was probably collapsed out
NSString *formatStr = @"measure cannot find view with tag #%@";
NSString *errMsg = [NSString stringWithFormat:formatStr, hippyTag];
HippyLogWarn(formatStr, hippyTag);
if (withErrMsg) {
NSString *errMsg = [NSString stringWithFormat:formatStr, hippyTag];
callback(@[@{HippyUIManagerGetBoundingErrMsgrKey : errMsg}]);
} else {
callback(@[]);
}
callback(@[@{HippyUIManagerGetBoundingErrMsgrKey : errMsg}]);
return;
}
UIView *rootView = viewRegistry[view.rootTag];
if (!rootView) {
NSString *formatStr = @"measure cannot find view's root view with tag #%@";
NSString *errMsg = [NSString stringWithFormat:formatStr, hippyTag];
HippyLogWarn(formatStr, hippyTag);
if (withErrMsg) {
NSString *errMsg = [NSString stringWithFormat:formatStr, hippyTag];
callback(@[@{HippyUIManagerGetBoundingErrMsgrKey : errMsg}]);
} else {
callback(@[]);
}
callback(@[@{HippyUIManagerGetBoundingErrMsgrKey : errMsg}]);
return;
}

Expand All @@ -1346,37 +1334,30 @@ - (void)measureInWindow:(nonnull NSNumber *)hippyTag
@"y":@(windowFrame.origin.y)}]);
}];
}
// clang-format on

// clang-format off
HIPPY_EXPORT_METHOD(measureInAppWindow:(nonnull NSNumber *)hippyTag
callback:(HippyResponseSenderBlock)callback) {
// keep the same as the old version, no errMsg return
[self measureInAppWindow:hippyTag withErrMsg:NO callback:callback];
}

- (void)measureInAppWindow:(nonnull NSNumber *)hippyTag
withErrMsg:(BOOL)withErrMsg
callback:(HippyResponseSenderBlock)callback {
callback:(HippyResponseSenderBlock)callback) {
[self addUIBlock:^(__unused HippyUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
UIView *view = viewRegistry[hippyTag];
if (!view) {
// this view was probably collapsed out
NSString *formatStr = @"measure cannot find view with tag #%@";
NSString *errMsg = [NSString stringWithFormat:formatStr, hippyTag];
HippyLogWarn(formatStr, hippyTag);
if (withErrMsg) {
NSString *errMsg = [NSString stringWithFormat:formatStr, hippyTag];
callback(@[@{HippyUIManagerGetBoundingErrMsgrKey : errMsg}]);
} else {
callback(@[]);
}
callback(@[@{HippyUIManagerGetBoundingErrMsgrKey : errMsg}]);
return;
}

CGRect windowFrame = [view.window convertRect:view.frame fromView:view.superview];
callback(@[@{@"width":@(CGRectGetWidth(windowFrame)),
@"height": @(CGRectGetHeight(windowFrame)),
@"x":@(windowFrame.origin.x),
@"y":@(windowFrame.origin.y)}]);
}];
}
// clang-format on

#pragma mark -

Expand Down
2 changes: 1 addition & 1 deletion packages/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ declare namespace HippyTypes {
// The height of component
height: number | undefined;
// error message
errMsg?: string | undefined;
errorMsg?: string | undefined;
}

export interface DOMRect {
Expand Down
8 changes: 4 additions & 4 deletions packages/hippy-react/src/modules/ui-manager-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,8 +175,8 @@ function measureInWindowByMethod(
if (callback && isFunction(callback)) {
callback(layout);
}
if (!layout || typeof layout !== 'object') {
return reject(new Error(`${method} error with response: ${layout}`));
if (layout === 'this view is null') {
return reject(new Error('Android cannot get the node'));
}
return resolve(layout);
});
Expand Down Expand Up @@ -225,8 +225,8 @@ function getBoundingClientRect(ref: Fiber, options: { relToContainer?: boolean }
}
trace('UIManagerModule', { nodeId, funcName: 'getBoundingClientRect', params: options });
return Bridge.callNative('UIManagerModule', 'getBoundingClientRect', nodeId, options, (res: HippyTypes.LayoutEvent) => {
if (!res || res.errMsg) {
return reject(new Error((res?.errMsg) || 'getBoundingClientRect error with no response'));
if (!res || res.errorMsg) {
return reject(new Error((res?.errorMsg) || 'getBoundingClientRect error with no response'));
}
const { x, y, width, height } = res;
let bottom: undefined | number = undefined;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ describe('runtime/native.ts', () => {
) => {
const [, , callback] = args;
callback({
errMsg: 'this view is null',
errorMsg: 'this view is null',
});
},
});
Expand Down
35 changes: 23 additions & 12 deletions packages/hippy-vue-next/src/runtime/native/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -345,27 +345,38 @@ export const measureInWindowByMethod = async (
width: -1,
height: -1,
};

if (!el.isMounted || !el.nodeId) {
return Promise.resolve(empty);
}

const { nodeId } = el;
return new Promise(resolve => Native.callNative(
'UIManagerModule',
method,
nodeId,
(pos: NativePosition | string) => {
if (!pos || typeof pos !== 'object' || typeof nodeId === 'undefined') {
// Android error handler.
if (
!pos
|| pos === 'this view is null'
|| typeof nodeId === 'undefined'
) {
return resolve(empty);
}
const { x, y, height, width } = pos;
return resolve({
top: y,
left: x,
width,
height,
bottom: y + height,
right: x + width,
});

if (typeof pos !== 'string') {
return resolve({
top: pos.y,
left: pos.x,
bottom: pos.y + pos.height,
right: pos.x + pos.width,
width: pos.width,
height: pos.height,
});
}

return resolve(empty);
},
));
};
Expand Down Expand Up @@ -637,8 +648,8 @@ export const Native: NativeApiType = {
}
trace('UIManagerModule', { nodeId, funcName: 'getBoundingClientRect', params: options });
Native.callNative('UIManagerModule', 'getBoundingClientRect', nodeId, options, (res) => {
if (!res || res.errMsg) {
return reject(new Error((res?.errMsg) || 'getBoundingClientRect error with no response'));
if (!res || res.errorMsg) {
return reject(new Error((res?.errorMsg) || 'getBoundingClientRect error with no response'));
}
const { x, y, width, height } = res;
let bottom: undefined | number = undefined;
Expand Down
Loading

0 comments on commit 8dadbaf

Please sign in to comment.