How can I bring a SKSpriteNode to the front of all other node?
With UIView, I can use bringSubviewToFront to bring an uiview in front of other views.
You can't "bring it to front" but you can change the zPosition of it.
The higher the zPosition of the node the later it gets rendered.
So if you have three nodes with zPositions of -1, 0 and 1 then the -1 will appear at the back. Then the 0. Then 1 will appear at the front.
If they all use the default zPosition of 0.0 then they are rendered in the order they appear in the children array of the parent node.
You can read it all in the docs.
https://developer.apple.com/documentation/spritekit/sknode
SWIFT 4
I usually create enums to establish zPositions so that I can simply manage the different layers:
enum ZPositions: Int {
case background
case foreground
case player
case otherNodes
}
“case background” is the lower position, it’s equal to the default position of 0;
“case foreground” = 1
“case player” = 2
“case other nodes” = 3
So, when you setup a new item, you can give it the zPosition simply this way:
button.zPosition = CGFloat(ZPosition.otherNodes.rawValue)
(button now has the highest zPosition)
This extension adds a bringToFront method to SKNode. This great for times when zPosition is inappropriate, and you want to rely on sibling ordering.
extension SKNode {
func bringToFront() {
guard let parent = parent else { return }
removeFromParent()
parent.addChild(self)
}
}
`
If you want to bring a node to the very front use this:
yourNode.zPosition = 1
or to the very back:
yourNode.zPosition = -1
or the same level as other nodes(as it's set to this value by default):
yourNode.zPosition = 0
Related
I have a custom LayoutManager (inherited from LinearLayoutManager) that needs to calculate the item width of each child and remove all children from RecyclerView that has no space for them to appear.
Sample code (edited V2):
override fun onLayoutChildren(recycler: RecyclerView.Recycler, state: RecyclerView.State) {
super.onLayoutChildren(recycler, state)
// skip if orientation is vertical, for now we only horizontal custom menu
if (orientation == RecyclerView.VERTICAL) return
// skip if adapter has no items
if (itemCount == 0) return
var totalItemWidth = 0
var totalItemsCanFit = 0
// calculate menu item width and figure out how many items can fit in the screen
for (i in 0 until childCount) {
getChildAt(i)?.let { childView ->
totalItemWidth += getDecoratedMeasuredWidth(childView)
}
if (screenWidth > totalItemWidth) {
totalItemsCanFit++
}
}
// if all items can fit, do nothing and show the whole menu
if (childCount > totalItemsCanFit) {
// remove child views that have no space on screen
for (i in childCount - 1 downTo totalItemsCanFit) {
removeAndRecycleViewAt(i, recycler)
}
}
}
I have 2 questions:
Is the sample code above the correct way to approach this problem?
How can I add a 3-dot icon at the end after seeing that not all items could fit?
EDIT:
To clarify, what I am trying to achieve is a popup menu backed by a RecyclerView. The menu has no item limit, instead it should calculate each item width and remove all items that have no space. Also, add a 3-dot menu item as a more option at the end.
Regarding your first question:
See if addDisappearingView(View child) could help you,
according to the documentation:
To be called only during onLayoutChildren(Recycler, State) to add a
view to the layout that is known to be going away, either because it
has been removed or because it is actually not in the visible portion
of the container but is being laid out in order to inform RecyclerView
in how to animate the item out of view.
As for the second question - you simply need to implement a 'load more' feature to your recyclerView. How you'll implement this is up to your needs/design (if you want a button or auto-scroll...).
There are many tutorials for that, for example: https://androidride.com/android-recyclerview-load-more-on-scroll-example/ .
I'm making pretty heavy use of SKCropNode in my game both for stylistic uses and also in one case where I've built my own SpriteKit version of UIScrollView. I've noticed that when I get a touch event or when a gesture recognizer fires at a certain point, SKScene.nodeAtPoint(...) is still hitting nodes that are hidden at the touch point from the crop node.
How do I prevent SKScene.nodeAtPoint(...) from hitting cropped content, and instead return the next visible node beneath it?
You can't. Everything is based on the frame of the content, and with crop nodes, it is huge. The only thing you can do is check the point of touch, use nodesAtPoint to get all nodes, then enumerate one by one, checking whether or not the touch point is touching a visible pixel by either checking the pixel data, or having a CGPath outlining of your sprite and checking if you are inside that.
I managed to work my way around this one, but it isn't pretty and it doesn't work in every scenario.
The idea is, when a touch is recorded, I iterate over all nodes at that point. If any of those nodes are (or are children of) SKCropNodes, I check the frame of the mask on the crop node. If the touch lies outside the mask, we know the node is not visible at that point and we can assume the touch didn't hit it.
The issue with this method is that I don't do any evaluation of transparency within the crop mask - I just assume it's a rectangle. If the mask is an abnormal shape, I may give false positives on node touches.
extension SKScene {
// Get the node at a given point, but ignore those that are hidden due to SKCropNodes.
func getVisibleNodeAtPoint(point: CGPoint) -> SKNode? {
var testedNodes = Set<SKNode>()
// Iterate over all the nodes hit by this click.
for touchedNode in self.nodesAtPoint(point) {
// If we've already checked this node, skip it. This happens because nodesAtPoint
// returns both leaf and ancestor nodes, and we test the ancestor nodes while
// testing leaf nodes.
if testedNodes.contains(touchedNode) {
continue
}
var stillVisible = true
// Walk the ancestry chain of the target node starting with the touched
// node itself.
var currentNode: SKNode = touchedNode
while true {
testedNodes.insert(currentNode)
if let currentCropNode = currentNode as? SKCropNode {
if let currentCropNodeMask = currentCropNode.maskNode {
let pointNormalizedToCurrentNode = self.convertPoint(point, toNode: currentCropNode)
let currentCropNodeMaskFrame = currentCropNodeMask.frame
// Check if the touch is inside the crop node's mask. If not, we
// know we've touched a hidden point.
if
pointNormalizedToCurrentNode.x < 0 || pointNormalizedToCurrentNode.x > currentCropNodeMaskFrame.size.width ||
pointNormalizedToCurrentNode.y < 0 || pointNormalizedToCurrentNode.y > currentCropNodeMaskFrame.size.height
{
stillVisible = false
break
}
}
}
// Move to next parent.
if let parent = currentNode.parent {
currentNode = parent
} else {
break
}
}
if !stillVisible {
continue
}
// We made it through the ancestry nodes. This node must be visible.
return touchedNode
}
// No visible nodes found at this point.
return nil
}
}
I implemented drag and drop of images and now i want to constrain proportions of images while resizing.
/**
* Variable: constrainChildrenOnResize
*
* Specifies if children should be constrained according to the <constrainChildren>
* switch if cells are resized (including via <foldCells>). Default is false for
* backwards compatiblity.
*/
mxGraph.prototype.constrainChildrenOnResize = false;
i set this to true but its not working :s
What API/property i need for this functionality..
constrainChildrenOnResize is responsible for positioning and size of the children of resized cell. It means that children should keep their position relatively to the parent-cell.
In your case I would suggest to extend mxVertexHandler using union method. In this example you can see how to implement min-width/min-height restrictions. Using this example you are able to write your own rules for constrain.
Here is my simple solution:
var vertexHandlerUnion = mxVertexHandler.prototype.union;
mxVertexHandler.prototype.union = function (bounds) {
var result = vertexHandlerUnion.apply(this, arguments);
var coff = bounds.width / bounds.height
result.width = result.height * coff;
return result;
};
So this function is called every time you move mouse during dragging the resizer.
bounds - object, always same and represent old geometry of the cell (before resizing)
result - object, represents new values, which are going to be applied. Between this line ad return statement you can place any code you need to modify result.
In my simple example I just get the initial relation between width and height of the cell (coff) and then set new width by multiplying coff and new height. It will work if you drag corner or top/bottom. In real project this logic should be slightly extended, or you should make visible only corner handlers.
By the way, this code will work for all resizable cells on your graph. If you want to apply it only to images or some other kind of cells - you can put condition and check the cell type before recalculating. You can get current cell or its state via this.state.cell or this.state inside of union function.
For example only for vertecies:
... ...
var result = vertexHandlerUnion.apply(this, arguments);
if (this.state.cell.isVertex()) {
//calculations here
}
return result;
I have a SKSpriteNode that moves with the accelerometer by using the following code:
-(void)processUserMotionForUpdate:(NSTimeInterval)currentTime {
SKSpriteNode* ship = (SKSpriteNode*)[self childNodeWithName:#"fishderp"];
CMAccelerometerData* data = self.motionManager.accelerometerData;
if (fabs(data.acceleration.y) > 0.2) {
[gameFish.physicsBody applyForce:CGVectorMake(0, data.acceleration.y)];
}
}
This works well however, the node (gamefish) moves off the screen. How can I prevent this and have it stay on the screen?
Try using an SKConstraint which was designed exactly for this purpose and introduced in iOS8:
Just add this to the setup method of the gameFish node. The game engine will apply the constraint after the physics has run. You won't have to worry about it. Cool huh?
// get the screensize
CGSize scr = self.scene.frame.size;
// setup a position constraint
SKConstraint *c = [SKConstraint
positionX:[SKRange rangeWithLowerLimit:0 upperLimit:scr.width]
Y:[SKRange rangeWithLowerLimit:0 upperLimit:scr.width]];
gameFish.constraints = #[c]; // can take an array of constraints
The code depends on whether you have added the gameFish node to self or to another node (something like a "worldNode"). If you have added it to self, look at the code below:
// get the screen height as you are only changing your node's y
float myHeight = self.view.frame.size.height;
// next check your node's y coordinate against the screen y range
// and adjust y if required
if(gameFish.position.y > myHeight) {
gameFish.position = CGPointMake(gameFish.position.x, myHeight);
}
For the bottom you can do a check of < 0 or whatever value you need.
I am trying to imitate what Apple has when showing the search result in the App Store. (reference: http://searchengineland.com/apple-app-search-shows-only-one-result-at-a-time-133818)
It shows like the detailed-application-info in a cards and it is paged. I am stuck at how to make the previous-and-next card shows when one active card in the middle and the scroll view's paging behaviour is still intact.
I have tried using the UICollectionView and set the clipSubviews to NO, hoping that it will show the previous page and the next page, but as soon as the cell goes off-screen, the cell gets hidden (removed from the view hierarchy) and not displayed. I think thats the flyweight pattern of the UICollectionView (the behavior of UICollectionView). Any ideas of what would be possible?
Cheers,
Rendy Pranata
The problem: UICollectionView as a subclass of UIScrollView essentially animates its bounds by a stride of bounds.size. Although this could mean that all you had to do is decrease the bounds while keeping the frame bigger, unfortunately UICollectionView will not render any cells outside its current bounds... destroying your preview effect.
The Solution:
Create a UICollectionView with paging set to NO and with the desired frame.
Create UICollectionViewCells that are smaller than the UICollectionView's frame/bounds. At this stage, a part of the next cell should show in the frame. This should be visible before implementing the other steps below.
Add a collectionView.contentInset.left and right (I assume your layout is horizontal) equal to the contentOffsetValue method (as shown below for simplicity) so as to align the first and last cells to the middle.
Create a UICollectionViewFlowLayout which overrides the method that gives the stopping point like so:
Like so:
-(CGFloat)contentOffsetValue
{
return self.collectionView.bounds.size.width * 0.5f - self.itemSize.width * 0.5f;
}
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity
{
static float EscapeVelocity = 0.5f; // otherwise snap back to the middle
NSArray* layoutAttributesArray = [self layoutAttributesForElementsInRect:self.collectionView.bounds];
if(layoutAttributesArray.count == 0)
return proposedContentOffset;
CGFloat currentBoundsCenterX = self.collectionView.contentOffset.x + self.collectionView.bounds.size.width * 0.5f;
UICollectionViewLayoutAttributes* candidateNextLayoutAttributes = layoutAttributesArray.firstObject;
for (UICollectionViewLayoutAttributes* layoutAttributes in layoutAttributesArray)
{
if ((layoutAttributes.representedElementCategory != UICollectionElementCategoryCell) ||
(layoutAttributes == candidateNextLayoutAttributes)) // skip the first comparison
continue;
if(velocity.x > EscapeVelocity || velocity.x < -(EscapeVelocity))
{
if(velocity.x > EscapeVelocity && layoutAttributes.center.x > candidateNextLayoutAttributes.center.x)
{
candidateNextLayoutAttributes = layoutAttributes;
}
else if (velocity.x < -(EscapeVelocity) && layoutAttributes.center.x < candidateNextLayoutAttributes.center.x)
{
candidateNextLayoutAttributes = layoutAttributes;
}
}
else
{
if(fabsf(currentBoundsCenterX - layoutAttributes.center.x) < fabsf(currentBoundsCenterX - candidateNextLayoutAttributes.center.x))
{
candidateNextLayoutAttributes = layoutAttributes;
}
}
}
return CGPointMake(candidateNextLayoutAttributes.center.x - self.collectionView.bounds.size.width * 0.5f, proposedContentOffset.y);
}
I just put together a sample project which shows how you could do this. I created a container view which is 100 points wider than the 320 points for the screen. Then I put a UICollectionView into that container. This offsets everything by 50 points on both sides of the screen.
Then there is a content cell which simply has a background and a label so you can visually identify what is happening. On the left and right there are empty cells. In the viewDidLoad method the content inset is set to negative values on the left and right to make the empty cells now scroll into view. You can adjust the inset to your preference.
This mimics the behavior fairly closely. To get the label below, like in the example you can simply check the contentOffset value to determine which cell is in focus. To do that you'd use the UIScrollViewDelegate which is a part of UICollectionView.
https://github.com/brennanMKE/Interfaces/tree/master/ListView
You'll notice this sample project has 2 collection views. One is a normal horizontal flow layout while the other one which has larger cells is the one which mimics the example you mentioned.