How do I recognize an object inside shapeLayer from UIBeizierPath in Swift? Below is the example app PopAGraph. Thanks in advance.
https://vimeo.com/61877513
My code:
fileprivate func addNewPathToImage() {
path.lineJoinStyle = CGLineJoin.round
shapeLayer.path = path.cgPath
shapeLayer.strokeColor = strokeColor.cgColor
shapeLayer.fillColor = UIColor(red: 52/255, green: 152/255, blue: 219/255, alpha: 0.5).cgColor
shapeLayer.lineWidth = lineWidth
mainImageView.layer.addSublayer(shapeLayer)
}
My image and shapeLayer:
CAShapeLayer his a hitTest() function. In your superview:
private func setUpShapeLayer() {
self.isUserInteractionEnabled = true
let tapGesture = UITapGestureRecognizer()
tapGesture.numberOfTapsRequired = 1
tapGesture.addTarget(self, action: #selector(viewTapped))
self.addGestureRecognizer(tapGesture)
[your code to add shapeLayer]
}
internal func viewTapped(_ recognizer:UITapGestureRecognizer) {
let p = recognizer.location(in: self)
if (shapeLayer.hitTest(p) != nil) {
[do something here]
} else if (greenLayer.hitTest(p) != nil) {
}
EDIT: As mentioned in the comments, I totally misunderstood what the question was.
The link in the OP is to an app that looks to have some proprietary functions happening. If you wish for similar iOS "out of box" functionality, here's about the best you can do:
(1) Have the user create a CAShapeLayer by panning the outline.
(2) Run a CoreImage filter (CIEdges) on the image to detect all edges.
(3) Find the "best fit" of contiguous (closed with no endpoints) edges where the hitTest() of said edges lay within the CAShapeLayer and highlight it.
I've seen this video a couple of times, and it has me wondering - what if the user traced a shape that cut off the Statue of Liberty head? What would the app do? Cut off the statue head, recognize the full statue, or result in no recognition?
This last observation isn't meant to be opinion, but instead point out that this may be a demo that shows the "best case" features of the app. And if the user does something "less than optimal" or unexpected may in fact give you a better idea of how it accomplishes what it does.
Related
I'm working on trying to recreate the first SwiftUI Tutorial in code without using SwiftUI. In the example, it mentions using a Spacer "to direct the layout to use the full width of the device":
VStack {
Text("Turtle Rock")
HStack {
Text("Joshua Tree National Park")
Spacer()
Text("California")
}
}
For the VStack, HStack, and Text views, I can easily use UIStackView and UILabel. But for the Spacer, I can't seem to find any equivalent in the standard library (no UISpacer or anything like that), which makes me think this is something custom to SwiftUI. In the tutorial, it describes how this Spacer works:
A spacer expands to make its containing view use all of the space of its parent view, instead of having its size defined only by its contents.
So how can I recreate the functionality of this Spacer view programmatically? Do I just add a constraint to the UIStackView to make it full width? Or is there a way to add a subview to the UIStackView that makes it behave like it does in SwiftUI?
I have worked solution using dummy UIView() which has set big constant widthConstraint using low priority - this ensures UIView will grow as much as possible but will not grow over superview constraints since it has lower priority.
Example:
let titleLabel = UILabel()
titleLabel.text = "Joshua Tree National Park"
let spacer = UIView()
// maximum width constraint
let spacerWidthConstraint = spacer.widthAnchor.constraint(equalToConstant: .greatestFiniteMagnitude) // or some very high constant
spacerWidthConstraint.priority = .defaultLow // ensures it will not "overgrow"
spacerWidthConstraint.isActive = true
let descriptionLabel = UILabel()
descriptionLabel.text = "California"
UIStackView(arrangedSubviews: [titleLabel, spacer, descriptionLabel])
Swift 5
You can create an extension for UIView to create a spacer in this way:
extension UIView {
static func spacer(size: CGFloat = 10, for layout: NSLayoutConstraint.Axis = .horizontal) -> UIView {
let spacer = UIView()
if layout == .horizontal {
spacer.widthAnchor.constraint(equalToConstant: size).isActive = true
} else {
spacer.heightAnchor.constraint(equalToConstant: size).isActive = true
}
return spacer
}
}
After that, you can add your spacer into stacks in this way:
// ... some code
let stack = UIStackView(arrangedSubviews: [UIView.spacer(), UILabel(), UIView.spacer(), UILabel(), UIView.spacer()])
stack.axis = .horizontal
stack.distribution = .equalSpacing
// For vertical stacks
let stack = UIStackView(arrangedSubviews: [UIView.spacer(for: .vertical), UILabel(), UIView.spacer(for: .vertical), UILabel(), UIView.spacer(for: .vertical)])
stack.axis = .vertical
stack.distribution = .equalSpacing
I think in this way, you can play with spacer size/layout and stack distribution/alignment to get the desired space.
A plain UIView() can replace a SwiftUI.Spacer as long as distribution is set to fill, since it has no set width constraint. I've only tested this vertically. However, you should note the layout performed by UIStackView and SwiftUI.HStack are not actually the same steps, though the end results may appear similar; something to look into.
I am currently trying to modify the plane tracking demo that apple provided to have little cube projectiles bounce off the tracking planes that the app creates.
I have added physics bodies to the respective projectile and wall, but for some reason the projectile continues to ignore the collisions and just phases through the wall. I have tried adding and removing the bit masks, but they don't seem to affect anything.
Here is what I am currently doing for my tracked planes:
init (anchor: ARPlaneAnchor, in sceneView: ARSCNView) {
guard let meshGeometry = ARSCNPlaneGeometry(device: sceneView.device!) else {
fatalError("CANNOT CREATE GEOMETRY")
}
meshGeometry.update(from: anchor.geometry)
meshNode = SCNNode(geometry: meshGeometry)
meshNode.name = "ENV."
super.init()
self.setupMeshVisualStyle()
let body = SCNPhysicsBody(type: .static, shape: nil)
meshNode.physicsBody = body
addChildNode(meshNode)
}
Here is the bullet physics implementation
class func spawnBullet() -> SCNNode {
// Omitted code (just setting size and stuff)
let cubeNode = SCNNode(geometry: cubeGeometry)
cubeNode.name = "BULLET"
let physicsBody = SCNPhysicsBody(type: SCNPhysicsBodyType.dynamic, shape: nil)
physicsBody.isAffectedByGravity = false
physicsBody.damping = 0
physicsBody.categoryBitMask = CollisionCategory.bullet.rawValue
physicsBody.collisionBitMask = CollisionCategory.env.rawValue | CollisionCategory.player.rawValue
physicsBody.continuousCollisionDetectionThreshold = 0.01;
cubeNode.physicsBody = physicsBody
return cubeNode
}
I have created a separate SCNPlane object with the exact same physics body settings as the tracked plane settings. What's odd is that the bullets successfully collide off of the SCNPlane, but they continue to pass through the tracked planes with the exact same settings.
In Cocoa/AppKit, given a screen from [NSScreen screens], how can I find out if there's a full-screen app running on that specific screen? I'm mostly interested in apps that use the Cocoa APIs for full-screen, but if there's a solution that also encompasses other types of full-screen apps, even better. The solution needs be able to pass Mac App Store approval.
My specific use case involves a menu bar app (NSStatusItem) and figuring out whether or not a menubar is shown at all on [NSScreen mainScreen] in order to allow a global keyboard shortcut to show either a popover positioning on the status item (if it's visible) or a floating window if there's no visible status item.
NSScreens themselves don't seem to expose any information about windows/apps, and NSRunningApplication doesn't expose this information either.
Are there perhaps Carbon APIs for finding this out? For example, if I have a list of windows, I could iterate through them and see if any window frames match the screens' frame exactly. On the other hand, there might be apps that have a frame like that but run underneath other apps (like the Backdrop app, https://itunes.apple.com/us/app/backdrop/id411461952?mt=12), so an approach like this would need to look at window levels.
You can try the CGWindowList API, such as CGWindowListCopyWindowInfo().
If you just want to know if the menu bar is showing, you should be able to check -[NSApplication currentSystemPresentationOptions] for NSApplicationPresentationAutoHideMenuBar or NSApplicationPresentationHideMenuBar. That method can also tell you if the active app is in Cocoa full-screen mode (NSApplicationPresentationFullScreen).
Here's a solution based on CGWindowListCopyWindowInfo in Swift.
func fullScreenWindows(fullScreen: Bool) -> [CGWindowID] {
var winList: [CGWindowID] = []
// if you want to get the windows in full screen, you MUST make sure the option excluding 'optionOnScreenOnly'
let option: CGWindowListOption = fullScreen ? .excludeDesktopElements : [.excludeDesktopElements, .optionOnScreenOnly]
guard let winArray: CFArray = CGWindowListCopyWindowInfo(option, kCGNullWindowID) else {
return winList
}
for i in 0..<CFArrayGetCount(winArray) {
// current window's info
let winInfo = unsafeBitCast(CFArrayGetValueAtIndex(winArray, i), to: CFDictionary.self)
// current window's bounds
guard let boundsDict = (winInfo as NSDictionary)[kCGWindowBounds],
let bounds = CGRect.init(dictionaryRepresentation: boundsDict as! CFDictionary) else {
continue
}
// to check the window is in full screen or not
guard __CGSizeEqualToSize(NSScreen.main!.frame.size, bounds.size) else {
continue
}
// current window's id
guard let winId = (winInfo as NSDictionary)[kCGWindowNumber] as? CGWindowID,
winId == kCGNullWindowID else {
continue
}
winList.append(winId)
}
return winList
}
Here's a solution based on CGWindowListCopyWindowInfo, as Ken Thomases suggested in his answer:
- (BOOL)fullScreenAppPresentOn:(NSScreen *)screen
{
// Get all of the visible windows (across all running applications)
NSArray<NSDictionary*> *windowInfoList = (__bridge_transfer id)CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements, kCGNullWindowID);
// For each window, see if the bounds are the same size as the screen's frame
for (int windowInfoListIndex = 0; windowInfoListIndex < (int)windowsInfoList.count; windowInfoListIndex++)
{
NSDictionary *windowInfo = windowInfoList[windowInfoListIndex];
CFDictionaryRef windowInfoRef = (__bridge CFDictionaryRef) windowInfo[(__bridge NSString *)kCGWindowBounds];
CGRect windowBounds;
CGRectMakeWithDictionaryRepresentation(windowInfoRef, &windowBounds);
if (CGRectEqualToRect([screen frame], windowBounds))
{
return YES;
}
}
return NO;
}
I'm trying to write a basic game using Apple's Sprite Kit framework. So far, I have a ship flying around the screen, using SKPhysicsBody. I want to keep the ship from flying off the screen, so I edited my update method to make the ship's velocity zero. This works most of the time, but every now and then, the ship will fly off the screen.
Here's my update method.
// const int X_MIN = 60;
// const int X_MAX = 853;
// const int Y_MAX = 660;
// const int Y_MIN = 60;
// const float SHIP_SPEED = 50.0;
- (void)update:(CFTimeInterval)currentTime {
if (self.keysPressed & DOWN_ARROW_PRESSED) {
if (self.ship.position.y > Y_MIN) {
[self.ship.physicsBody applyForce:CGVectorMake(0, -SHIP_SPEED)];
} else {
self.ship.physicsBody.velocity = CGVectorMake(self.ship.physicsBody.velocity.dx, 0);
}
}
if (self.keysPressed & UP_ARROW_PRESSED) {
if (self.ship.position.y < Y_MAX) {
[self.ship.physicsBody applyForce:CGVectorMake(0, SHIP_SPEED)];
} else {
self.ship.physicsBody.velocity = CGVectorMake(self.ship.physicsBody.velocity.dx, 0);
}
}
if (self.keysPressed & RIGHT_ARROW_PRESSED) {
if (self.ship.position.x < X_MAX) {
[self.ship.physicsBody applyForce:CGVectorMake(SHIP_SPEED, 0)];
} else {
self.ship.physicsBody.velocity = CGVectorMake(0, self.ship.physicsBody.velocity.dy);
}
}
if (self.keysPressed & LEFT_ARROW_PRESSED) {
if (self.ship.position.x > X_MIN) {
[self.ship.physicsBody applyForce:CGVectorMake(-SHIP_SPEED, 0)];
} else {
self.ship.physicsBody.velocity = CGVectorMake(0, self.ship.physicsBody.velocity.dy);
}
}
}
At first, I used applyImpulse in didBeginContact to push the ship back. This made the ship bounce, but I don't want the ship to bounce. I just want it to stop at the edge.
What is the right way to make the ship stop once it reaches the edge? The code above works most of the time, but every now and then the ship shoots off screen. This is for OS X—not iOS—in case that matters.
Check out this link...
iOS7 SKScene how to make a sprite bounce off the edge of the screen?
[self setPhysicsBody:[SKPhysicsBody bodyWithEdgeLoopFromRect:self.frame]]; //Physics body of Scene
This should set up a barrier around the edge of your scene.
EDIT:
This example project from Apple might also be useful
https://developer.apple.com/library/mac/samplecode/SpriteKit_Physics_Collisions/Introduction/Intro.html
Your code is not clear in what the velocity variables represent. Keep in mind that if the velocity is too high your ship will have travelled multiple points between updates. For example, your ship's X/Y is at (500,500) at the current update. Given a high enough velocity, your ship could be at (500,700) at the very next update. If you had your boundary set at (500,650) your ship would already be past it.
I suggest you do a max check on velocity BEFORE applying it to your ship. This should avoid the problem I outlined above.
As for bouncy, bouncy... did you try setting your ship's self.physicsBody.restitution = 0; ? The restitution is the bounciness of the physics body. If you use your own screen boundaries, then I would recommend setting those to restitution = 0 as well.
Your best bet would be to add a rectangle physics body around the screen (boundary). Set the collision and contact categories of the boundary and player to interact with each other. In the didBeginContact method you can check if the bodies have touched and, if they have, you can call a method to redirect the ship.
Your problem is that your update method may not be checking the location frequently enough before the ship gets off screen.
This will help you to define you screen edges in Swift.
self.physicsBody = SKPhysicsBody ( edgeLoopFromRect: self.frame )
I am developing a game using iOS SpriteKit. I am trying to make an object in this game that will pull things towards it and the force will get greater as objects come closer to it, think of a magnet or a black hole. I've been having a difficult time figuring out what properties to change to get this nodes physicsBody to attract other nodes as they pass by.
In iOS 8 and OS X 10.10, SpriteKit has SKFieldNode for creating forces that apply to bodies in an area. This is great for things like buoyancy, area-specific gravity, and "magnets".
Watch out, though — the magneticField you get from that class is probably not what you want for the kind of "magnets" gameplay you might be looking for. A magnetic field behaves as per real-world physics at the micro level... that is, it deflects moving, charged bodies. What we usually think of as magnets — the kind that stick to your fridge, pick up junked cars, or make a hoverboard fly — is a higher-level effect of that force.
If you want a field that just attracts anything (or some specific things) nearby, a radialGravityField is what you're really after. (To attract only specific things, use the categoryBitMask on the field and the fieldBitMask on the bodies it should/shouldn't interact with.)
If you want a field that attracts different things more or less strongly, or attracts some things and repels others, the electricField is a good choice. You can use the charge property of physics bodies to make them attracted or repelled (negative or positive values) or more or less strongly affected (greater or less absolute value) by the field.
Prior to iOS 8 & OS X 10.10, SpriteKit's physics simulation doesn't include such kinds of force.
That doesn't keep you from simulating it yourself, though. In your scene's update: method you can find the distances between bodies, calculate a force on each proportional to that distance (and to whatever strength of magnetic field you're simulating), and apply forces to each body.
yes you can create magnetic force in sprite kit
-(void)didSimulatePhysics
{
[self updateCoin];
}
-(void) updateCoin
{
[self enumerateChildNodesWithName:#"coin" usingBlock:^(SKNode *node, BOOL *stop) {
CGPoint position;
position=node.position;
//move coin right to left
position.x -= 10;
node.position = position;
//apply the magnetic force between hero and coin
[self applyMagnetForce:coin];
if (node.position.x <- 100)
[node removeFromParent];
}];
}
-(void)applyMagnetForce:(sprite*)node
{
if( gameViewController.globalStoreClass.magnetStatus)
{
//if hero under area of magnetic force
if(node.position.x<400)
{
node.physicsBody.allowsRotation=FALSE;
///just for fun
node.physicsBody.linearDamping=10;
node.physicsBody.angularVelocity=10*10;
//where _gameHero is magnet fulling object
[node.physicsBody applyForce:CGVectorMake((10*10)*(_gameHero.position.x- node.position.x),(10*10)*(_gameHero.position.y-node.position.y)) atPoint:CGPointMake(_gameHero.position.x,_gameHero.position.y)];
}
}
}
remember both hero and coin body need be dynamic
Well seems like now you can since Apple introduced SKFieldNode in iOS 8.
Well you use the following code snippets to do what you're looking for, but it doesn't have attraction and repulsion properties
Body code:
let node = SKSpriteNode(imageNamed: "vortex")
node.name = "vortex"
node.position = position
node.run(SKAction.repeatForever(SKAction.rotate(byAngle: CGFloat.pi, duration: 1)))
node.physicsBody = SKPhysicsBody(circleOfRadius: node.size.width / 2)
node.physicsBody?.isDynamic = false
node.physicsBody?.categoryBitMask = CollisionTypes.vortex.rawValue
node.physicsBody?.contactTestBitMask = CollisionTypes.player.rawValue
node.physicsBody?.collisionBitMask = 0
addChild(node)
Upon contact with the that blackhole body:
func playerCollided(with node: SKNode) {
if node.name == "vortex" {
player.physicsBody?.isDynamic = false
isGameOver = true
score -= 1
let move = SKAction.move(to: node.position, duration: 0.25)
let scale = SKAction.scale(to: 0.0001, duration: 0.25)
let remove = SKAction.removeFromParent()
let sequence = SKAction.sequence([move, scale, remove])
player.run(sequence) { [unowned self] in
self.createPlayer()
self.isGameOver = false
}
}