fix: safeguards against invalid values

This commit is contained in:
Igor Randjelovic
2021-02-26 18:49:50 +01:00
committed by Nathan Walker
parent 1dfa5a6025
commit f5db58414a
6 changed files with 154 additions and 54 deletions

View File

@ -11,13 +11,17 @@ export class BoxShadowModel extends Observable {
private _selectedBackgroundType: string; private _selectedBackgroundType: string;
private _selectedBorderType: string; private _selectedBorderType: string;
private _selectedAnimation: string; private _selectedAnimation: string;
private _boxShadow: string; private _boxShadow: string = '5 5 1 1 rgba(255, 0, 0, .9)';
background: string; background: string;
borderWidth: number; borderWidth: number;
borderRadius: number; borderRadius: number;
appliedBoxShadow: string; appliedBoxShadow: string;
get boxShadow(): string {
return this._boxShadow;
}
constructor() { constructor() {
super(); super();
} }

View File

@ -9,14 +9,14 @@
<!-- layouts --> <!-- layouts -->
<ScrollView height="100%" visibility="{{ selectedComponentType === 'layouts' ? 'visible' : 'collapsed' }}"> <ScrollView height="100%" visibility="{{ selectedComponentType === 'layouts' ? 'visible' : 'collapsed' }}">
<StackLayout> <StackLayout>
<StackLayout <StackLayout
width="300" width="300"
height="100" height="100"
class="demo-component" class="demo-component"
boxShadow="{{ appliedBoxShadow }}" boxShadow="{{ appliedBoxShadow }}"
borderWidth="{{ borderWidth }}" borderWidth="{{ borderWidth }}"
borderRadius="{{ borderRadius }}" borderRadius="{{ borderRadius }}"
backgroundColor="{{ background }}" background="{{ background }}"
tap="{{ toggleAnimation }}" tap="{{ toggleAnimation }}"
> >
<Label text="StackLayout"></Label> <Label text="StackLayout"></Label>
@ -29,7 +29,7 @@
boxShadow="{{ appliedBoxShadow }}" boxShadow="{{ appliedBoxShadow }}"
borderWidth="{{ borderWidth }}" borderWidth="{{ borderWidth }}"
borderRadius="{{ borderRadius }}" borderRadius="{{ borderRadius }}"
backgroundColor="{{ background }}" background="{{ background }}"
tap="{{ toggleAnimation }}" tap="{{ toggleAnimation }}"
> >
<Label text="GridLayout"></Label> <Label text="GridLayout"></Label>
@ -42,7 +42,7 @@
boxShadow="{{ appliedBoxShadow }}" boxShadow="{{ appliedBoxShadow }}"
borderWidth="{{ borderWidth }}" borderWidth="{{ borderWidth }}"
borderRadius="{{ borderRadius }}" borderRadius="{{ borderRadius }}"
backgroundColor="{{ background }}" background="{{ background }}"
tap="{{ toggleAnimation }}" tap="{{ toggleAnimation }}"
> >
<Label text="AbsoluteLayout"></Label> <Label text="AbsoluteLayout"></Label>
@ -55,7 +55,7 @@
boxShadow="{{ appliedBoxShadow }}" boxShadow="{{ appliedBoxShadow }}"
borderWidth="{{ borderWidth }}" borderWidth="{{ borderWidth }}"
borderRadius="{{ borderRadius }}" borderRadius="{{ borderRadius }}"
backgroundColor="{{ background }}" background="{{ background }}"
tap="{{ toggleAnimation }}" tap="{{ toggleAnimation }}"
> >
<Label text="DockLayout"></Label> <Label text="DockLayout"></Label>
@ -68,17 +68,33 @@
boxShadow="{{ appliedBoxShadow }}" boxShadow="{{ appliedBoxShadow }}"
borderWidth="{{ borderWidth }}" borderWidth="{{ borderWidth }}"
borderRadius="{{ borderRadius }}" borderRadius="{{ borderRadius }}"
backgroundColor="{{ background }}" background="{{ background }}"
tap="{{ toggleAnimation }}" tap="{{ toggleAnimation }}"
> >
<Label text="FlexboxLayout"></Label> <Label text="FlexboxLayout"></Label>
</FlexboxLayout> </FlexboxLayout>
<GridLayout
width="300"
height="100"
padding="4"
boxShadow="{{ appliedBoxShadow }}"
tap="{{ toggleAnimation }}"
>
<StackLayout
borderWidth="4"
borderRadius="20"
backgroundColor="white"
>
<Label text="BorderRadius + BoxShadow on parent container"></Label>
</StackLayout>
</GridLayout>
</StackLayout> </StackLayout>
</ScrollView> </ScrollView>
<!-- labels --> <!-- labels -->
<GridLayout <GridLayout
rows="*" rows="*"
height="100%" height="100%"
visibility="{{ selectedComponentType === 'labels' ? 'visible' : 'collapsed' }}"> visibility="{{ selectedComponentType === 'labels' ? 'visible' : 'collapsed' }}">
@ -90,32 +106,53 @@
boxShadow="{{ appliedBoxShadow }}" boxShadow="{{ appliedBoxShadow }}"
borderWidth="{{ borderWidth }}" borderWidth="{{ borderWidth }}"
borderRadius="{{ borderRadius }}" borderRadius="{{ borderRadius }}"
backgroundColor="{{ background }}" background="{{ background }}"
tap="{{ toggleAnimation }}" tap="{{ toggleAnimation }}"
text="Label"></Label> text="Label"></Label>
</GridLayout> </GridLayout>
<!-- buttons --> <!-- buttons -->
<GridLayout <GridLayout
rows="*" rows="*"
height="100%" height="100%"
visibility="{{ selectedComponentType === 'buttons' ? 'visible' : 'collapsed' }}"> visibility="{{ selectedComponentType === 'buttons' ? 'visible' : 'collapsed' }}">
<Button <Button
horizontalAlignment="center" horizontalAlignment="center"
verticalAlignment="center" verticalAlignment="center"
class="demo-component" class="demo-component"
boxShadow="{{ appliedBoxShadow }}" boxShadow="{{ appliedBoxShadow }}"
borderWidth="{{ borderWidth }}" borderWidth="{{ borderWidth }}"
borderRadius="{{ borderRadius }}" borderRadius="{{ borderRadius }}"
backgroundColor="{{ background }}" background="{{ background }}"
tap="{{ toggleAnimation }}" tap="{{ toggleAnimation }}"
text="button" text="button"
></Button> ></Button>
</GridLayout> </GridLayout>
<!-- images -->
<GridLayout
rows="*"
height="100%"
visibility="{{ selectedComponentType === 'images' ? 'visible' : 'collapsed' }}">
<ContentView width="100"
height="100"
horizontalAlignment="center"
verticalAlignment="center"
class="demo-component"
boxShadow="{{ appliedBoxShadow }}"
borderWidth="{{ borderWidth }}"
borderRadius="{{ borderRadius }}"
tap="{{ toggleAnimation }}">
<Image src="https://raw.githubusercontent.com/NativeScript/artwork/main/logo/export/NativeScript_Logo_Blue_Transparent.png"></Image>
</ContentView>
</GridLayout>
</StackLayout> </StackLayout>
<GridLayout <GridLayout
@ -130,6 +167,7 @@
<TextField <TextField
col="1" col="1"
placeholder="box-shadow" placeholder="box-shadow"
text="{{ boxShadow }}"
textChange="{{ textChange }}" textChange="{{ textChange }}"
> >
</TextField> </TextField>
@ -146,6 +184,7 @@
<Button text="Layouts" componentType="layouts" tap="{{ selectComponentType }}"></Button> <Button text="Layouts" componentType="layouts" tap="{{ selectComponentType }}"></Button>
<Button text="Labels" componentType="labels" selectedAttr="{{ selectedComponentType }}" tap="{{ selectComponentType }}"></Button> <Button text="Labels" componentType="labels" selectedAttr="{{ selectedComponentType }}" tap="{{ selectComponentType }}"></Button>
<Button text="Buttons" componentType="buttons" selectedAttr="{{ selectedComponentType == 'buttons' }}" tap="{{ selectComponentType }}"></Button> <Button text="Buttons" componentType="buttons" selectedAttr="{{ selectedComponentType == 'buttons' }}" tap="{{ selectComponentType }}"></Button>
<Button text="Images" componentType="images" selectedAttr="{{ selectedComponentType == 'images' }}" tap="{{ selectComponentType }}"></Button>
</FlexboxLayout> </FlexboxLayout>
<Label text="Background"></Label> <Label text="Background"></Label>
@ -172,7 +211,7 @@
<Button text="Scale" animationType="scale" tap="{{ selectAnimationType }}"></Button> <Button text="Scale" animationType="scale" tap="{{ selectAnimationType }}"></Button>
<Button text="Rotate" animationType="rotate" tap="{{ selectAnimationType }}"></Button> <Button text="Rotate" animationType="rotate" tap="{{ selectAnimationType }}"></Button>
</FlexboxLayout> </FlexboxLayout>
</StackLayout> </StackLayout>
</ScrollView> </ScrollView>
</GridLayout> </GridLayout>

View File

@ -134,6 +134,7 @@ function unsubscribeFromScrollNotifications(view: View) {
view.off('scroll', onScroll); view.off('scroll', onScroll);
} }
} }
function subscribeForScrollNotifications(view: View) { function subscribeForScrollNotifications(view: View) {
if (view.nativeViewProtected instanceof UIScrollView) { if (view.nativeViewProtected instanceof UIScrollView) {
view.on('scroll', onScroll); view.on('scroll', onScroll);
@ -170,6 +171,7 @@ function clearNonUniformBorders(nativeView: NativeView): void {
} }
const pattern = /url\(('|")(.*?)\1\)/; const pattern = /url\(('|")(.*?)\1\)/;
function setUIColorFromImage(view: View, nativeView: UIView, callback: (uiColor: UIColor) => void, flip?: boolean): void { function setUIColorFromImage(view: View, nativeView: UIView, callback: (uiColor: UIColor) => void, flip?: boolean): void {
const frame = nativeView.frame; const frame = nativeView.frame;
const boundsWidth = view.scaleX ? frame.size.width / view.scaleX : frame.size.width; const boundsWidth = view.scaleX ? frame.size.width / view.scaleX : frame.size.width;
@ -733,15 +735,24 @@ function drawBoxShadow(nativeView: NativeView, view: View, boxShadow: CSSShadow,
} }
// shadow opacity is handled on the shadow's color instance // shadow opacity is handled on the shadow's color instance
layer.shadowOpacity = boxShadow.color?.a ? boxShadow.color?.a / 255 : 1; layer.shadowOpacity = boxShadow.color?.a ? boxShadow.color?.a / 255 : 1;
layer.shadowRadius = Length.toDevicePixels(boxShadow.blurRadius); layer.shadowRadius = Length.toDevicePixels(boxShadow.blurRadius, 0.0);
layer.shadowColor = boxShadow.color.ios.CGColor; layer.shadowColor = boxShadow.color.ios.CGColor;
layer.shadowOffset = CGSizeMake(Length.toDevicePixels(boxShadow.offsetX), Length.toDevicePixels(boxShadow.offsetY));
// prettier-ignore
layer.shadowOffset = CGSizeMake(
Length.toDevicePixels(boxShadow.offsetX, 0.0),
Length.toDevicePixels(boxShadow.offsetY, 0.0)
);
// this should match the view's border radius // this should match the view's border radius
const cornerRadius = Length.toDevicePixels(<LengthType>view.style.borderRadius); const cornerRadius = Length.toDevicePixels(<LengthType>view.style.borderRadius, 0.0);
// apply spreadRadius by expanding shadow layer bounds // apply spreadRadius by expanding shadow layer bounds
const bounds = boxShadow.spreadRadius ? CGRectInset(nativeView.bounds, -Length.toDevicePixels(boxShadow.spreadRadius), -Length.toDevicePixels(boxShadow.spreadRadius)) : nativeView.bounds; // prettier-ignore
const bounds = CGRectInset(nativeView.bounds,
-Length.toDevicePixels(boxShadow.spreadRadius, 0.0),
-Length.toDevicePixels(boxShadow.spreadRadius, 0.0)
);
// This has the nice glow with box shadow of 0,0 // This has the nice glow with box shadow of 0,0
layer.shadowPath = UIBezierPath.bezierPathWithRoundedRectCornerRadius(bounds, cornerRadius).CGPath; layer.shadowPath = UIBezierPath.bezierPathWithRoundedRectCornerRadius(bounds, cornerRadius).CGPath;
@ -749,7 +760,10 @@ function drawBoxShadow(nativeView: NativeView, view: View, boxShadow: CSSShadow,
function clearBoxShadow(nativeView: NativeView) { function clearBoxShadow(nativeView: NativeView) {
nativeView.clipsToBounds = true; nativeView.clipsToBounds = true;
const layer: CALayer = iOSNativeHelper.getShadowLayer(nativeView, 'ns-box-shadow'); const layer: CALayer = iOSNativeHelper.getShadowLayer(nativeView, 'ns-box-shadow', false);
if (!layer) {
return;
}
layer.masksToBounds = true; layer.masksToBounds = true;
layer.shadowOffset = CGSizeMake(0, 0); layer.shadowOffset = CGSizeMake(0, 0);
layer.shadowColor = UIColor.clearColor.CGColor; layer.shadowColor = UIColor.clearColor.CGColor;

View File

@ -34,7 +34,7 @@ const isLength = (v) => v === '0' || LENGTH_RE.test(v);
* @param value * @param value
*/ */
export function parseCSSShadow(value: string): CSSShadow { export function parseCSSShadow(value: string): CSSShadow {
const parts = value.split(PARTS_RE); const parts = value.trim().split(PARTS_RE);
const inset = parts.includes('inset'); const inset = parts.includes('inset');
const first = parts[0]; const first = parts[0];
const last = parts[parts.length - 1]; const last = parts[parts.length - 1];

View File

@ -171,8 +171,9 @@ export namespace iOSNativeHelper {
/** /**
* @param nativeView UIView to find shadow layer with * @param nativeView UIView to find shadow layer with
* @param name Name of the shadow layer if looking for specifically named layer * @param name Name of the shadow layer if looking for specifically named layer
* @param create should we create a new layer if not found
*/ */
export function getShadowLayer(nativeView: any /* UIView */, name?: string): any; /* CALayer */ export function getShadowLayer(nativeView: any /* UIView */, name?: string, create?: boolean): any; /* CALayer */
/** /**
* Create a UIDocumentInteractionControllerDelegate implementation for use with UIDocumentInteractionController * Create a UIDocumentInteractionControllerDelegate implementation for use with UIDocumentInteractionController

View File

@ -120,44 +120,86 @@ export namespace iOSNativeHelper {
return transform; return transform;
} }
export function getShadowLayer(nativeView: UIView, name?: string): CALayer { export function getShadowLayer(nativeView: UIView, name: string = 'ns-shadow-layer', create: boolean = true): CALayer {
let layer: CALayer; return nativeView.layer;
name = name || 'ns-shadow-layer';
if (nativeView) { console.log(`--- ${create ? 'CREATE' : 'READ'}`);
if (nativeView.layer) {
if (nativeView.layer.name === name) { /**
console.log('- found shadow layer - reusing.'); * UIView
return nativeView.layer; * -> Shadow
} else { *
if (nativeView.layer.sublayers && nativeView.layer.sublayers.count) { *
console.log('nativeView.layer.sublayers.count:', nativeView.layer.sublayers.count); * UIView
for (let i = 0; i < nativeView.layer.sublayers.count; i++) { * -> UIView
console.log(`layer ${i}:`, nativeView.layer.sublayers.objectAtIndex(i)); * -> Shadow
console.log(`layer ${i} name:`, nativeView.layer.sublayers.objectAtIndex(i).name); */
if (nativeView.layer.sublayers.objectAtIndex(i).name === name) {
return nativeView.layer.sublayers.objectAtIndex(i); if (!nativeView) {
} return null;
} }
if (nativeView instanceof UITextView) {
layer = nativeView.layer.sublayers.objectAtIndex(1); if (!nativeView.layer) {
} else { // should never hit this?
layer = nativeView.layer.sublayers.objectAtIndex(nativeView.layer.sublayers.count - 1); console.log('- no layer! -');
} return null;
} else { }
layer = nativeView.layer;
} // if the nativeView's layer is the shadow layer?
if (nativeView.layer.name === name) {
console.log('- found shadow layer - reusing.');
return nativeView.layer;
}
console.log('>> layer :', nativeView.layer);
if (nativeView.layer.sublayers?.count) {
const count = nativeView.layer.sublayers.count;
for (let i = 0; i < count; i++) {
const subLayer = nativeView.layer.sublayers.objectAtIndex(i);
console.log(`>> subLayer ${i + 1}/${count} :`, subLayer);
console.log(`>> subLayer ${i + 1}/${count} name :`, subLayer.name);
if (subLayer.name === name) {
console.log('- found shadow sublayer - reusing.');
return subLayer;
} }
} else {
// could this occur?
console.log('no layer!');
} }
// if (nativeView instanceof UITextView) {
// return nativeView.layer.sublayers.objectAtIndex(1);
// } else {
// return nativeView.layer.sublayers.objectAtIndex(nativeView.layer.sublayers.count - 1);
// }
} }
console.log('layer.name:', layer.name); // else {
if (!layer.name) { // layer = nativeView.layer;
// only explicitly name if the developer had not named it themselves and/or some other integration // }
layer.name = name;
// we're not interested in creating a new layer
if (!create) {
return null;
} }
return layer;
console.log(`- adding a new layer for - ${name}`);
const viewLayer = nativeView.layer;
const newLayer = CALayer.layer();
newLayer.name = name;
newLayer.zPosition = 0.0;
// nativeView.layer.insertSublayerBelow(newLayer, nativeView.layer)
// newLayer.insertSublayerAtIndex(nativeView.layer, 0)
// nativeView.layer.zPosition = 1.0;
// nativeView.layer.addSublayer(newLayer);
// nativeView.layer = CALayer.layer()
nativeView.layer.insertSublayerAtIndex(newLayer, 0);
// nativeView.layer.insertSublayerAtIndex(viewLayer, 1)
// nativeView.layer.replaceSublayerWith(newLayer, nativeView.layer);
return newLayer;
} }
export function createUIDocumentInteractionControllerDelegate(): NSObject { export function createUIDocumentInteractionControllerDelegate(): NSObject {