value type of constant in Swift in layout anchors - autolayout

Just to know the difference between 10 (for example) and view.frame.height/10 (where view.frame.height is 100)
I put textView.frame.height/3 instead of of 25 in paddingTop (see code below) and the value of paddingTop becomes 0
I tried this .....
extension UIView
{ func anchor( top: NSLayoutYAxisAnchor?,
left: NSLayoutXAxisAnchor?,
bottom: NSLayoutYAxisAnchor?,
right: NSLayoutXAxisAnchor?,
paddingTop: CGFloat,
paddingLeft: CGFloat,
paddingBottom: CGFloat,
paddingRight: CGFloat,
width: CGFloat,
height: CGFloat )
{ translatesAutoresizingMaskIntoConstraints = false
if let top = top { self.topAnchor.constraint(equalTo: top, constant: paddingTop).isActive = true }
if let left = left { self.leftAnchor.constraint(equalTo: left, constant: paddingLeft).isActive = true }
if let bottom = bottom { self.bottomAnchor.constraint(equalTo: bottom, constant: -paddingBottom).isActive = true }
if let right = right { self.rightAnchor.constraint(equalTo: right, constant: -paddingRight).isActive = true }
if width != 0 { widthAnchor.constraint(equalToConstant: width).isActive = true }
if height != 0 { heightAnchor.constraint(equalToConstant: height).isActive = true }
}
...
view.addSubview(textView)
textView.anchor(top: topView.bottomAnchor, left: view.leftAnchor, bottom: nil, right: nil, paddingTop: 10, paddingLeft: 10, paddingBottom: 0, paddingRight: 10, width: 0, height: view.frame.height/4)
textView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
textView.addSubview(label)
label.anchor(top:textView.topAnchor, left: textView.leftAnchor, bottom: nil, right: nil, paddingTop: 25, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height:textView.frame.height/5)
counterLabel.centerXAnchor.constraint(equalTo: descrizioneTextView.centerXAnchor).isActive = true

So the reason why you're experiencing that problem is because at the moment you're initialising the view, the value (view.frame.height) is 0 and you can't get view.frame.height/4.
Since you're working with UIView, Try to set the padding top in this method :
override func layoutSubviews() {
super.layoutSubviews()
}

Related

Understanding CSS triangles and rhombus interactions in React native with absolute positions

I have a complex structure diagram with Rhombuses and triangles. I was able to create the structure using CSS tricks and absolute position property.
I want each rhombus and triangle to be clickable but the behavior is not as expected given how CSS triangles work with the borders.
An output image with some markers
In this image we have a rhombus and two triangles,
What I want:
On clicking at A,B,E - rhombus is clicked
On clicking C,D - first triangle (numbered 2 in picture) is clicked.
What happens instead:
On clicking A, nothing happens (I suspect the absolute and transform property here)
On clicking B, first triangle (numbered 2 in picture) is clicked. It is because of the transparent right border of first triangle.
On clicking C, second triangle (numbered 3 in picture) is clicked. It is because of the transparent top border of second triangle.
On clicking D, first triangle is clicked which is the expected behaviour.
On clicking E, rhombus is clicked which is the expected behaviour.
How can I make such complex interactive patterns with series of such shapes? Shall I consider using images/SVGs instead of CSS shapes? I also need to add multiple dynamic count of numbers/text/emojis in each shape.
Here's a simplified snippet of code:
import React from 'react';
import {
View,
TouchableOpacity,
StyleSheet,
SafeAreaView,
} from 'react-native';
const MyComplexDiagram = ({navigation, route}) => {
const clickShape = text => {
console.log('clicked: ', text);
};
return (
<SafeAreaView style={{flex: 1}}>
<View>
<TouchableOpacity onPress={() => clickShape("rhombus)}>
<View style={[styles.rhombus]} />
</TouchableOpacity>
<TouchableOpacity onPress={() => clickShape("first triangle")}>
<View style={[styles.firstTriangle]} />
</TouchableOpacity>
<TouchableOpacity onPress={() => clickShape("second triangle")}>
<View style={[styles.secondTriangle]} />
</TouchableOpacity>
</SafeAreaView>
);
};
export default MyComplexShape;
const styles = StyleSheet.create({
rhombus: {
position: 'absolute',
top: 50,
left: 100,
width: 100,
height: 100,
backgroundColor: 'green',
transform: [{rotate: '45deg'}],
},
firstTriangle: {
position: 'absolute',
top: 0,
left: 0,
backgroundColor: 'transparent',
borderStyle: 'solid',
borderLeftWidth: 50,
borderRightWidth: 50,
borderTopWidth: 50,
borderLeftColor: 'transparent',
borderRightColor: 'transparent',
borderTopColor: 'red',
}),
secondTriangle: {
position: 'absolute',
top: 0,
left: 0,
backgroundColor: 'transparent',
borderStyle: 'solid',
borderLeftWidth: 50 * Math.sqrt(2),
borderTopWidth: 50 * Math.sqrt(2),
borderBottomWidth: 50 * Math.sqrt(2),
borderLeftColor: 'black',
borderBottomColor: 'transparent',
borderTopColor: 'transparent',
}
});
Im not sure if the same thing happens in a browser context, but the issue with the css trick is that although the view appears to be a triangle, it is actually still a rectangle. Giving the triangles a non-transparent background color will demonstrate this. The middle triangle is intercepting the first triangle presses because its hidden rectangle entirely overlaps the first triangle. (Here's a demo showing that). You can overcome this by the other TouchableOpacities a higher zIndex than the middle one:
import React, { useMemo, useEffect, useRef, useState } from 'react';
import {
View,
TouchableOpacity,
StyleSheet,
SafeAreaView,
useWindowDimensions,
Button,
Text
} from 'react-native';
import Animated, {
useSharedValue,
useAnimatedStyle,
withTiming,
withSequence,
withRepeat,
cancelAnimation,
withDelay,
} from 'react-native-reanimated';
const getHypotenuse = (side1, side2) => {
if (!side2) side2 = side1;
return Math.sqrt(side1 ** 2 + side2 ** 2);
};
const startRotation = 270;
const MyComplexDiagram = ({ navigation, route }) => {
const [animationIsRunning, setAnimationIsRunning] = useState(false);
const rotation = useSharedValue(startRotation);
const backgroundColor = useSharedValue('rgba(0,0,0,0)');
const { width, height } = useWindowDimensions();
const rotationStyle = useAnimatedStyle(() => {
return {
transform: [{ rotate: `${rotation.value}deg` }],
};
});
const colorStyle = useAnimatedStyle(() => {
return {
backgroundColor: backgroundColor.value,
};
});
const { rhombusStyle, middleTriangleStyle, leftTriangleStyle } =
useMemo(() => {
const remainingWidth =
width - styles.container.padding - styles.shapeContainer.padding;
const rhombusSize = remainingWidth * 0.3;
const rhombusHypotenuse = getHypotenuse(rhombusSize);
const rhombusLeft = remainingWidth - rhombusHypotenuse;
const rhombusTop = 25;
const middleLeft = rhombusLeft - rhombusSize;
const leftLeft = middleLeft;
return {
rhombusStyle: {
width: rhombusSize,
height: rhombusSize,
left: rhombusLeft,
top: rhombusTop,
},
middleTriangleStyle: {
borderWidth: rhombusSize * 0.8,
borderLeftWidth: rhombusSize * 0.8,
left: rhombusLeft - rhombusSize * 1.1,
top: 10,
},
leftTriangleStyle: {
borderWidth: rhombusSize,
borderBottom: rhombusSize,
left: rhombusLeft - rhombusHypotenuse * 1.7,
top: 0,
},
};
}, [width]);
const clickShape = (text) => {
console.log('clicked: ', text);
};
const toggleAnimation = () => {
if (animationIsRunning) {
backgroundColor.value = withTiming('rgba(0,0,0,0)');
} else {
backgroundColor.value = withRepeat(
withSequence(
withTiming('rgba(255,0,0,1)', { duration: 2000 }),
withTiming('rgba(0,0,0,0'),
withDelay(4000,withTiming('rgba(0,0,0,0'))
),
-1
);
}
setAnimationIsRunning((prev) => !prev);
};
return (
<SafeAreaView style={styles.container}>
<Button
title={`${animationIsRunning ? 'Hide' : 'Show'} Triangle Background`}
onPress={toggleAnimation}
/>
<Text style={{zIndex:5}}>{animationIsRunning ? 'Using zIndex to force black triangle to be on top of orange':'Default behavior without zIndex changes'}</Text>
<View style={styles.shapeContainer}>
<TouchableOpacity
style={animationIsRunning && { zIndex: 5 }}
onPress={() => clickShape('leftTriangle')}>
<Animated.View
style={[styles.leftTriangle, leftTriangleStyle, colorStyle]}
/>
</TouchableOpacity>
<TouchableOpacity
style={animationIsRunning && { zIndex: 4 }}
onPress={() => clickShape('middleTriangle')}>
<Animated.View
style={[
styles.middleTriangle,
middleTriangleStyle,
rotationStyle,
colorStyle,
]}
/>
</TouchableOpacity>
<TouchableOpacity
style={animationIsRunning && { zIndex: 5 }}
onPress={() => clickShape('rhombus')}>
<Animated.View style={[styles.rhombus, rhombusStyle]} />
</TouchableOpacity>
</View>
</SafeAreaView>
);
};
export default MyComplexDiagram;
const styles = StyleSheet.create({
container: {
flex: 1,
// alignItems:'center',
justifyContent: 'center',
padding: 8,
},
shapeContainer: {
padding: 10,
},
rhombus: {
transform: [{ rotate: '45deg' }],
position: 'absolute',
top: 0,
bottom: 0,
right: 0,
left: 0,
backgroundColor: 'green',
},
middleTriangle: {
position: 'absolute',
top: 0,
bottom: 0,
right: 0,
left: 0,
width: 0,
height: 0,
borderColor: 'transparent',
borderRightColor: 'orange',
},
leftTriangle: {
position: 'absolute',
transform: [{ rotate: '225deg' }],
top: 0,
bottom: 0,
right: 0,
left: 0,
width: 0,
height: 0,
borderColor: 'transparent',
borderLeftColor: 'black',
},
});

How to set height to an element by his background image height

Hi I have this component in my app, Right now the height of this ButonBase is hard coded.
I get an image from backend and I set that image as a backgroundImage for the ImageImagine component.
I need to make height of that ButonBase to bee dinamic so the background image in imageImagine can mentain original aspect ratio.
import React, { useState } from 'react';
import {
Box,
ButtonBase as MuiButtonBase,
IconButton,
Typography,
styled,
} from '#mui/material';
import Label from './Label';
import Favorite from './Favorite';
import MintingContextButton from './MintingContextButton';
import StatusLabel from 'src/components/StatusLabel';
import PublicLabel from 'src/components/PublicLabel';
import { IMAGINE_STATUS } from 'src/constants';
import { authDomain } from 'src/config';
const ButtonBase = styled(MuiButtonBase)(({ theme }) => ({
position: 'relative',
height: 342,
width: '100%',
borderRadius: 32,
[theme.breakpoints.down('xs')]: {
width: '100% !important', // Overrides inline-style
height: 326,
},
'&:hover': {
'& .imageTitle': {
opacity: 1,
transition: theme.transitions.create('opacity'),
zIndex: 1,
},
'& .imageBackdrop': {
opacity: 0.7,
},
'& .MuiIconButton-favorite': {
opacity: 1,
transition: theme.transitions.create('opacity'),
zIndex: 12,
},
'& .MuiButton-minting': {
opacity: 1,
transition: theme.transitions.create('opacity'),
zIndex: 12,
},
},
}));
const ContextContainer = styled('span')(() => ({
position: 'absolute',
width: '100%', // should on if real mint button
height: '100%',
display: 'flex',
flexDirection: 'column',
justifyContent: 'space-between',
padding: 16,
zIndex: 1, // Remove if real mint button
}));
const StyledStatusLabel = styled(StatusLabel)(() => ({
position: 'absolute',
left: 16,
top: 16,
zIndex: 1,
}));
const MintIconButton = styled(IconButton)(() => ({
position: 'absolute',
left: 16,
top: 16,
zIndex: 1,
background: 'rgba(0, 0, 0, 0.7)',
'&:hover': {
background: 'rgba(128, 58, 254, 0.9)',
},
}));
const ImageTitle = styled('span')(({ theme }) => ({
opacity: 0,
textAlign: 'left',
// [theme.breakpoints.down('sm')]: {
// opacity: 1,
// zIndex: 1
// },
}));
const ImageBackdrop = styled('span')(({ theme }) => ({
position: 'absolute',
left: 0,
right: 0,
top: 0,
bottom: 0,
backgroundColor: theme.palette.common.black,
opacity: 0,
transition: theme.transitions.create('opacity'),
borderRadius: 32,
// [theme.breakpoints.down('sm')]: {
// opacity: 0.7,
// },
}));
const ImageImagine = styled('span')(() => ({
position: 'absolute',
left: 0,
right: 0,
top: 0,
bottom: 0,
backgroundSize: 'cover',
backgroundPosition: 'center 40%',
borderRadius: 32,
}));
const GalleryItem = ({
imagine,
imagine: {
id,
title,
status,
isMyFavorite,
public: publicStatus,
context: imgContext,
thumb_url: imgUrl,
count_favorites: countFavorites,
},
onOpen,
onClose,
...props
}) => {
const [isHoverFavorite, setHoverFavorite] = useState(false);
return (
<>
<ButtonBase
className="MuiButtonBase-gallery"
disableRipple
focusRipple
onClick={(event) => onOpen(event, id)}
>
<ContextContainer>
<Box display="flex" justifyContent="space-between">
<Box
display="flex"
visibility={status === IMAGINE_STATUS.COMPLETE && 'hidden'}
>
<StyledStatusLabel status={status} />
</Box>
{status === IMAGINE_STATUS.COMPLETE && (
<React.Fragment>
<Favorite
imagineId={id}
isMyFavorite={isMyFavorite}
isHoverFavorite={isHoverFavorite}
isHidden
onHoverFavorite={setHoverFavorite}
count={countFavorites}
/>
{/* <MintingContextButton imagine={imagine} /> */}
</React.Fragment>
)}
</Box>
<ImageTitle className="imageTitle">
<Typography color="textPrimary" variant="subtitle1" noWrap>
{title}
</Typography>
<Box
display="flex"
justifyContent="space-between"
alignItems="center"
>
{imgContext && <Label>{imgContext}</Label>}
<PublicLabel publicStatus={publicStatus} />
</Box>
</ImageTitle>
</ContextContainer>
<ImageImagine
style={{
backgroundImage: `url(${
imgUrl ?? `${authDomain}/images/glitchcat.gif`
})`,
}}
/>
<ImageBackdrop className="imageBackdrop" />
</ButtonBase>
</>
);
};
export default GalleryItem;
Had you tried to set the height of the ButtonBase into height: auto and the min-height into min-height: fit-content?

how to removing Top shadow of material-top-tabs?

Current Behavior
Top side of tabBar has shadow.
Expected Behavior
how to remove top shadow of top tabBar?
tried elevation: 0 but it also removes bottom shadow.
Side note - How top shadow was achieved for Tabs.Navigator (react navigation)? as box-shadow properties does not work for android and elevation only shows shadow to bottom.
How to reproduce
<>
<Header /> //App name custom component
<Tabs.Navigator
...
tabBarOptions={{
....
style: {
// elevation: 0,
},
}}>
Try with this:
<>
<Header /> //App name custom component
<Tabs.Navigator
...
tabBarOptions={{
....
style: {
elevation: 0,
shadowColor: "#000000",
shadowOffset: { width: 0, height: 10 }, // change this for more shadow
shadowOpacity: 0.4,
shadowRadius: 6
},
}}>
shadowOffset: { width: 0, height: 10 } shadows place only in bottom of View.
shadowOffset: { width: 0, height: -10 } shadows place only in top of View.
shadowOffset: { width: 10, height: 0 } shadows place only in right of View.
shadowOffset: { width: -10, height: 10 } shadows place only in left of View.
Found this example here.

Concatenating props within Inline CSS (React)

I'm currently trying to use props within my inline CSS. I'm curious to know why can't I concatenate this.props.color when using linear-gradient on the background-image property. Is there possibly another way I could go about achieving this or am I missing something?
render() {
let background = {
width: "100%",
height: "100%",
position: "fixed",
left: 0,
top: 0,
backgroundImage: "linear-gradient(to right," + this.props.color + "0%, #0072ff 100%)"
};
return (
<div style={background}></div>
);
}
Component in use:
<Background color='red'/>
You need a space before 0%
Do like the following:
render() {
let background = {
width: "100%",
height: "100%",
position: "fixed",
left: 0,
top: 0,
backgroundImage: `linear-gradient(to right,${this.props.color} 0%, #0072ff 100%)`
};
return (
<div style={background}></div>
);
}

How to render multiple shadows on one element?

For example, how to accomplish
box-shadow: 2px 2px 4px rgba(0, 0, 0, .05), -2px -2px 4px rgba(0, 0, 0, .05);
in react native stylesheet?
I don't think you can, but the hack of wrapping your component with another just for another layer of shadow is the worst hack of the century either:
<div style={{ boxShadow: "2px 2px 4px rgba(0, 0, 0, .05)"}}>
<div style={{ boxShadow: "-2px -2px 4px rgba(0, 0, 0, .05)"}}>
{ content }
</div>
</div>
I created a react component that automatically creates multiple View components for every shadow you need. It is likely to have some quirks, but it worked fine for my situation.
import React from 'react';
import PropTypes from 'prop-types';
import { View } from 'react-native';
import * as _ from 'lodash';
const partitionByKeys = (keys, obj) => {
let pass = {};
let fail = {};
for (const key of Object.keys(obj)) {
if (keys.includes(key)) {
pass[key] = obj[key];
} else {
fail[key] = obj[key];
}
}
return [pass, fail];
};
const innerStyleKeys = [
'padding', 'paddingTop', 'paddingBottom', 'paddingLeft', 'paddingRight',
'paddingHorizontal', 'paddingVertical',
'backgroundColor', 'flexDirection', 'justifyContent', 'alignItems',
'minHeight', 'minHeight',
];
const ShadowView = ({ level = 0, shadows, children, style, ...props }) => {
const shadow = _.head(shadows);
const [innerStyle, outerStyle] = style ? partitionByKeys(innerStyleKeys, style) : [{}, {}];
return (
<View
{...props}
style={{
shadowColor: shadow.color,
shadowOffset: shadow.offset,
shadowOpacity: shadow.opacity,
shadowRadius: shadow.radius,
...(level === 0 ? outerStyle : {}),
...(shadows.length === 1 ? innerStyle : {}),
}}
>
{ shadows.length === 1 ?
children :
<ShadowView level={level + 1} shadows={_.tail(shadows)} style={style}>{children}</ShadowView>
}
</View>
);
};
export default ShadowView;
Usage:
<ShadowView shadows={[{
color: '#FF0000',
opacity: 0.5,
offset: {
width: 0,
height: 10,
},
radius: 60,
}, {
color: '#00FF00',
opacity: 0.5,
offset: {
width: 0,
height: 3,
},
radius: 20,
}]}>...</ShadowView>

Resources