ASyncDisplayKit - Multiple nodes in one container - ios

Is it possible to have multiple Texture nodes inside one container?
For example, I need to have an ASTableNode on the top half of a screen, and an ASDisplayNode with a UIView block underneath that.
I have tried using an ASViewController with a layoutSpecThatFits which returns an ASSTackLayoutSpec - but neither of the subnodes appear on the view.
Thank you!

Yes, it is possible to place both the ASTableNode and ASDisplayNode in the same stack. Use a horizontal stack layout, set the nodes flexGrow property to equal values (so they grow equally) and set automaticallyManagesSubnodes to true on your node.
override init() {
///Initialize nodes if not initialized on declaration
super.init()
automaticallyManagesSubnodes = true
}
override func layoutSpecThatFits(constrainedSize: ASSizeRange) -> ASLayoutSpec {
let stack = ASStackLayoutSpec.vertical()
stack.children = [tableNode, displayNode]
tableNode.style.flexGrow = 1
displayNode.style.flexGrow = 1
///For testing
displayNode.backgroundColor = .red
tableNode.backgroundColor = .blue
}

Related

How do you add a CAGradientLayer to a UIBackgroundConfiguration in the new collection views with compositional layouts?

I'd like to add a gradient to a collection view cell's background in the context of the new collection view with compositional layouts. Here's an example of how a cell's background is configured from Apple's sample code Implementing Modern Collection Views in line 180 of EmojiExplorerViewController:
func configuredGridCell() -> UICollectionView.CellRegistration<UICollectionViewCell, Emoji> {
return UICollectionView.CellRegistration<UICollectionViewCell, Emoji> { (cell, indexPath, emoji) in
var content = UIListContentConfiguration.cell()
content.text = emoji.text
content.textProperties.font = .boldSystemFont(ofSize: 38)
content.textProperties.alignment = .center
content.directionalLayoutMargins = .zero
cell.contentConfiguration = content
var background = UIBackgroundConfiguration.listPlainCell()
background.cornerRadius = 8
background.strokeColor = .systemGray3
background.strokeWidth = 1.0 / cell.traitCollection.displayScale
cell.backgroundConfiguration = background
}
}
Since the new UIBackgroundConfiguration is a structure rather than a layer-backed UIView subclass, I can't just add a CAGradientLayer instance as a sublayer.
What would be a good approach to adding a gradient to a cell background configuration?
Since the new UIBackgroundConfiguration is a structure rather than a layer-backed UIView subclass, I can't just add a CAGradientLayer instance as a sublayer.
Yes, you can. The fact that UIBackgroundConfiguration is a struct is irrelevant. It has a customView property that's a view, and that will be used as the background view (behind the content view) in the cell. So set that view to something (it is nil by default) and you're all set.
Here's an example. This is a toy table view for test purposes, but the test is exactly about configuration objects, so it is readily adaptable to demonstrate the technique. It doesn't matter whether you're using a table view, a collection view, or neither, as long as you are using something that has a UIBackgroundConfiguration property. As you can see, I've made a vertical gradient from black to red as the background to my cells.
Here's the relevant code. First, I have defined a gradient-carrier view type:
class MyGradientView : UIView {
override static var layerClass: AnyClass { CAGradientLayer.self }
}
Then, I use that view as the background view when I configure the cell:
var back = UIBackgroundConfiguration.listPlainCell()
let v = MyGradientView()
(v.layer as! CAGradientLayer).colors =
[UIColor.black.cgColor, UIColor.red.cgColor]
back.customView = v
cell.backgroundConfiguration = back
Whatever else you want to achieve is merely a variant of the above. For example, you could use an image view or a solid background view and combine them with the gradient view. The point is, the customView is the background view, and whatever view you set it to will be displayed in the background of the cell.
I should also point out that there is another way to do this, namely, to use a cell subclass and implement updateConfigurationUsingState:. The advantage of this approach is that once you've given the background configuration a customView, you can just modify that customView each time the method is called. You can use this technique to respond to selection, for example, as I have demonstrated in other answers here (such as https://stackoverflow.com/a/63064099/341994).

Bottom corners arent round

I'm having an issue with some container views.
The main view has 2-container views. One of them is just a container view, straight up from the drop-off menu with nothing more than it's child VC being modified via Storyboard. (the embedded one).
The second Container View has a Scroll-view inside and a second view inside the container view with a custom size. (I saw a tutorial on youtube).
Now on my viewdidload on the HomeVC I call the following code:
func setupViews() {
containerTop.layer.cornerRadius = 15
containerTop.clipToBounds = true
containerBot.layer.cornerRadius = 15
containerBot.clipToBounds = true
}
The above code results in THIS RESULTS. As you can see all but 2 corners are rounded (the bottom 2 corners of the top view). Why is this happening and how can I fix this?
I'm not 100% sure about why you are having this odd behaviour, I also experienced something similar and fixed like following: (Your project should be supporting iOS11++ in order for this solution to work:
func setupViews() {
containerTop.layer.cornerRadius = 15
containerTop.clipToBounds = true
containerBot.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner, .layerMaxXMaxYCorner]
containerBot.layer.cornerRadius = 15
containerBot.clipToBounds = true
}
Thank you for the suggestion. Unfortunately it didnt work.
I was able to fix it by doing the following:
On the storyboard tree On the ViewController there is a View on the element tree. I linked that as an outlet in my code and made it's corners round. Basically I called the round corners twice:
- Home View controller had:
func setupViews() {
containerTop.layer.cornerRadius = 15
containerTop.clipToBounds = true
containerBot.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner, .layerMaxXMaxYCorner]
containerBot.layer.cornerRadius = 15
containerBot.clipToBounds = true
}
And then the ContainerTopViewController had inside a View (per storyboard) and the code is:
class ContainerTopViewController: UIViewController {
<LINKED THROUGH STORYBOARD> myView: UIViewController
func loadView() {
myView.layer.cornerRadius = 15
myView.layer.clipToBounds = true
}
}

How to create a Collision Detection between a label and an Image

INTRUDUCTION
I want to create a simple game where you should be able to drag a label and if you drag it in the correct place, you win. To be more specific: This is a game to help children with autism. In this game they have to create the correct sequence of numbers from one to ten dragging the label with the number in the correct place (which is an image actually). now you will understand better:
PROBLEM
I have already created the code to drag the labels (with a pan gesture recognizer) but I don't know how to create a Collision detection: When the card "1" is dragged and it collides with the blue image "1" something happens,
my mentally code is:
if LB_1 *collides with* IMG_1 {
self.IMG_1?.backgroundColor = UIColor.green
}
I hope the question it's clear.
Ah I don't use SpriteKit. I used "SingleViewApplication" as Template not "Game".
You want to use contains(). If you use intersects() then it will return true as soon as the two rectangles touch. With contains(), it won't return true until the dragged view is fully inside the target view, which seems much more intuitive.
I just wrote a sample app that implements this and it works perfectly.
I created a simple subclass of UIView I called BoxedView that just sets a border around it's layer so you can see it.
I set up a view controller with 2 boxed views, a larger "targetView", and a smaller view that the user could drag.
The target/action for my gesture recognizer moves the dragged view's frame as the user drags, and if target view contains the dragged view, it sets a Bool highlightTargetView, which causes the box around the target view to get thicker.
The entire view controller class' code looks like this:
class ViewController: UIViewController {
#IBOutlet weak var targetView: BoxedView!
var viewStartingFrame: CGRect = CGRect.zero
var highlightTargetView: Bool = false {
didSet {
targetView.layer.borderWidth = highlightTargetView ? 5 : 1
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func userDraggedView(_ gesture: UIPanGestureRecognizer) {
switch gesture.state {
case .began:
viewStartingFrame = gesture.view?.frame ?? CGRect.zero
case .changed:
let offset = gesture.translation(in: view)
gesture.view?.frame = viewStartingFrame.offsetBy(dx: offset.x, dy: offset.y)
highlightTargetView = targetView.frame.contains(gesture.view?.frame ?? CGRect.zero)
case .ended:
gesture.view?.frame = viewStartingFrame
highlightTargetView = false
default:
break
}
}
}
In order for the math to work, I use the frame property of both views, which is in the coordinate system of the parent view (The parent view is the view controller's content view in this case, but the key thing is that we compare 2 rectangles using the same coordinate system for both.) If you used the bounds property of either view your math wouldn't work because bounds of a view is in the local coordinate system of that view.
Here's what that program looks like when running:
For comparison, I modified the program to also show what it looks like using the intersects() function, and created a video of what that looks like:
You can check if their frames intersect.
CGRect has method intersects.
So you're if statement should be the following:
if LB_1.frame.intersects(IMG_1.frame) {
self.IMG_1?.backgroundColor = UIColor.green
}
If that's not enough for you, you can calculate area of intersection rect.

Card.io Override frame getter

I'm using Card.io to scan cards inside a custom UIView. The issue I have is that the camera view is taking up the view frame resulting in borders left and right. There's a frame property called cameraPreviewFrame that assigns the window and I think I need to override this property and return it's width and height.
Is this possible or is there something else I need to do?
My current code is:
var cardScanView: CardIOView = {
let csv = CardIOView()
csv.guideColor = .blue
csv.hideCardIOLogo = true
csv.allowFreelyRotatingCardGuide = false
csv.backgroundColor = .purple
return csv
}()
I'm adding this programatically inside a collection view cell and it does work in it's current form. Just the view is off.
Thanks

Insert sublayer behind UIImageView

i'm trying to add a sublayer behind the imageView however the issue is that since it is using constraints it can't seem to figure out the position and just places sublayer in left corner? i've tried to add the LFTPulseAnimation to viewDidLayoutSubViews but then everytime i reopen the app it will add one on top.
viewDidLoad
//GroupProfile ImageView
imageGroupProfile = UIImageView(frame: CGRect.zero)
imageGroupProfile.backgroundColor = UIColor.white
imageGroupProfile.clipsToBounds = true
imageGroupProfile.layer.cornerRadius = 50
self.view.addSubview(imageGroupProfile)
imageGroupProfile.snp.makeConstraints { (make) -> Void in
make.height.equalTo(100)
make.width.equalTo(100)
make.centerX.equalTo(self.view.snp.centerX)
make.centerY.equalTo(self.view.snp.centerY).offset(-40)
}
let pulseEffect = LFTPulseAnimation(repeatCount: Float.infinity, radius:160, position:imageGroupProfile.center)
self.view.layer.insertSublayer(pulseEffect, below: imageGroupProfile.layer)
i've tried to add the LFTPulseAnimation to viewDidLayoutSubViews but then everytime i reopen the app it will add one on top.
Nevertheless that is the way to do it. Just add a Bool property so that your implementation of viewDidLayoutSubViews inserts the layer only once:
var didLayout = false
override func viewDidLayoutSubviews() {
if !didLayout {
didLayout = true
// lay out that layer here
}
}
The reason is that you don't have the needed dimensions until after viewDidLayoutSubviews tells you that (wait for it) your view has been laid out! But, as you rightly say, it can be called many times subsequently, so you also add the condition so that your code runs just once, namely the first time viewDidLayoutSubviews is called.

Resources