Swift UIImage cant be animated with gravity - swift

I've looked into everything on Google, and that is not much. Is the community for Swift really this small???
I have an image, and on a long-press, I want it to fall down with gravity and hit the bottom of the screen.
The error I get is
Cannot convert value of type 'UIView' to expected argument type
'[UIDynamicItem]'
I have tried UILabel, UIImage, UIImageView, Rect, UIView i get this error on what ever i do.
My goal is to use UIImage or UIImageView.
This is the code I'm using for the animation:
var animator: UIDynamicAnimator!
var gravity: UIDynamicBehavior!
var collision : UICollisionBehavior!
var redBoxView: UIView?
#IBOutlet weak var detailsImageWeather: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
animator = UIDynamicAnimator(referenceView: self.view)
let imageTap = UILongPressGestureRecognizer(target: self, action: #selector(imageTapped))
detailsImageWeather.addGestureRecognizer(imageTap)
}
#objc func imageTapped() {
var frameRect = CGRect(x: 150, y: 20, width: 60, height: 60)
redBoxView = UIView(frame: frameRect)
redBoxView?.backgroundColor = UIColor.red
self.view.addSubview(redBoxView!)
let image = detailsImageWeather.image // This is what i want to use instead of redBoxView
gravity = UIGravityBehavior(items: redBoxView!)
animator.addBehavior(gravity)
collision = UICollisionBehavior (items: redBoxView!)
collision.translatesReferenceBoundsIntoBoundary = true
animator.addBehavior(collision)
let behavior = UIDynamicItemBehavior(items: [redBoxView!])
behavior.elasticity = 2
}
What am i doing wrong? cant find any more things to try on google

The error
Cannot convert value of type 'UIView' to expected argument type '[UIDynamicItem]'
tells that you need an array of UIDynamicItem. It's easy to miss the little square brackets.
You're actually configuring your UIGravityBehavior and UICollisionBehavior with redBoxView (an object) instead of [redBoxView] (an array). That's why you get the error.
You need to change your configurations to this
gravity = UIGravityBehavior(items: [redBoxView!]) //redBoxView passed in an array
and this
collision = UICollisionBehavior (items: [redBoxView!]) //redBoxView passed in an array

Related

Swift - Sprite Kit Physics - Toggle Dynamic to true and false

I have a simple app where I'm creating a shape dynamically. This shape has physics, but starts out with it's dynamics set to false (as intended).
var dot = SKSpriteNode(imageNamed: "ShapeDot.png");
override func sceneDidLoad() {
dot.name = "MyShapeDot";
dot.size = CGSize(width: 10,height: 10);
dot.position = CGPoint(x: 0, y: 0);
dot.physicsBody = SKPhysicsBody(circleOfRadius: CGFloat(dot.size.width/2))
dot.physicsBody?.isDynamic = false;
dot.physicsBody?.allowsRotation = false;
dot.physicsBody?.pinned = false;
dot.physicsBody?.affectedByGravity = true;
//add to spritekit scene
self.addChild(dot)
}
The shape is successfully added to the .sks and the controller (I see it on the screen). Then on a tap gesture I'm calling a function to turn on dynamics for the physics sprite node.
func MyTapGesture(){
dot.physicsBody?.isDynamic = true;
}
The MyTapGesture is being called (I debugged that it triggers), but the shape doesn't become dynamic and start using gravity... Does anyone know what I'm missing???
I'm calling the MyTapGesture from my interfaceController... It's wired up as so
let gameScene = GameScene();
#IBOutlet weak var spriteTapGestures: WKTapGestureRecognizer!
#IBAction func onSpriteTap(_ sender: Any) {
NSLog("tap")
gameScene. MyTapGesture()
}
Within the MyTapGesture I've also tried print(dot) and it outputs the following:
name:'MyShapeDot' texture:[<SKTexture> 'ShapeDot.png' (128 x 128)] position:{0, 0} scale:{1.00, 1.00} size:{10, 10} anchor:{0.5, 0.5} rotation:0.00
This leads me to believe it should work and I'm calling the right reference of the class that's attached to the object. But it doesn't work. If I call MyTapGesture() within the update func of the SpriteKit class where my dot was created
override func update(_ currentTime: TimeInterval) {
MyTapGesture()
}
It works and the dynamics update! ...so for some reason my tap gesture must be calling a wrong reference or something??? So confused since the debug shows the correct data printed for the shape that I created...
To solve this - I realized that my gameScene var in my interface controller didn't have the correct reference. So I instantiated it as nil:
var gameScene : GameScene?;
And then assigned the variable in the interface controllers awake func
if let scene = GameScene(fileNamed: "GameScene") {
gameScene = scene
}

Value of type 'SecondPageController' has no member 'miniView'

I got seems similar but little bit different question.
It is about declare size issue, but have small error here.
I think error is related to Main.stroyboard, and tried to change ViewController name and also added 'miniView' to ViewController.
My error is
"Value of type 'SecondPageController' has no member 'miniView'",
Witch is very common error(I looked many of same question with this format), but I think my error is relate to adding some new view or something.
And here is my code.
class SecondPageController: UIViewController {
#IBOutlet weak var mainScrollView: UIScrollView!
var imageArray = [UIImage]()
var currentPage = 0
override func viewDidLoad() {
super.viewDidLoad()
imageArray = [UIImage(named: "bus1")!, UIImage(named: "bus2")!]
for i in 0..<imageArray.count {
let imageView = UIImageView()
imageView.image = imageArray[i]
imageView.contentMode = .scaleAspectFit
let xPosition = self.miniView.frame.width * CGFloat(i)
imageView.frame = CGRect(x: xPosition, y: 0, width: self.mainScrollView.frame.width, height: self.mainScrollView.frame.height)
mainScrollView.contentSize.width = mainScrollView.frame.width * CGFloat(i + 1)
mainScrollView.addSubview(imageView)
}
Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(startAnimating), userInfo: nil, repeats: true)
}
#objc func startAnimating() {
//that line check if the current page is the last one, and set it to the first, otherwise, it increment the currentPage
self.currentPage = (self.currentPage == imageArray.count-1) ? 0 : self.currentPage+1
var newOffset = mainScrollView.contentOffset
newOffset.x = mainScrollView.frame.width * CGFloat(self.currentPage)
self.mainScrollView.setContentOffset(newOffset, animated: true)
}
}
I expecting add something to this error solved.
There is no member miniView in SecondPageController. The error was very precise.
If you wanna use a component named miniView you need a reference to it, you can use 2 approaches:
1 - Storyboard:
Drag a view to your UIViewController and link it to #IBOutlet weak var miniView: UIView!
2 - Programatic:
Instantiate it like: let miniView = UIView()
Since you are using Storyboards, you probably forgot to add an #IBOutletto that view, so use approach 1.

How to crop UIImage to mask in Swift

I have a UIImage that I mask using another UIImage. The only problem is area outside the masked UIImage is still user interactable. How do I completely crop a UIImage to another image instead of mask.
#IBOutlet weak var imageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
let imageMask = UIImageView()
imageMask.image = //image to mask
imageMask.frame = photoImageView.bounds
imageView.mask = imageMask
}
Criteria
A simple test case could define a background color for the view of the ViewController and load the image and mask. Then a UITapGestureRecognizer is added to the ViewController view and to the UIImageView.
When applying a background color to the ViewController view, it is easy to see if masking works.
If you then tap on a non-transparent area, the tap should be received by the UIImageView, otherwise the ViewController view should receive the tap.
Image and Mask Image Size
In most cases, the image and mask image size or at least the aspect ratio of the image and mask image is the same.
It makes sense to use the same contentMode for the masking of UIImageView as for the original UIImageView, otherwise there would be a misalignment when changing the content mode in InterfaceBuilder at the latest.
Test Case
Therefore the test case could look like this:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var imageView: UIImageView!
private let maskView = UIImageView()
override func viewDidLoad() {
super.viewDidLoad()
self.imageView.image = UIImage(named: "testimage")
self.maskView.image = UIImage(named: "mask")
self.imageView.mask = maskView
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(backgroundTapped))
self.view.addGestureRecognizer(tapGestureRecognizer)
let imageViewGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(iamgeViewTapped))
self.imageView.addGestureRecognizer(imageViewGestureRecognizer)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
self.maskView.contentMode = self.imageView.contentMode
self.maskView.frame = self.imageView.bounds
}
#objc private func backgroundTapped() {
print ("background tapped!")
}
#objc private func iamgeViewTapped() {
print ("image view tapped!")
}
}
This code is already running. As expected, however, taps on the transparent area of the UIImageView also get here.
CustomImageView
Therefore we need a CustomImageView, which returns when clicking on a transparent pixel that it is not responsible for it.
This can be achieved by overriding this method:
func point(inside point: CGPoint,
with event: UIEvent?) -> Bool
see documentation here: https://developer.apple.com/documentation/uikit/uiview/1622533-point
Returns a Boolean value indicating whether the receiver contains the specified point.
There is this cool answer already on SO, that is just slightly adapted: https://stackoverflow.com/a/27923457
import UIKit
class CustomImageView: UIImageView {
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
return self.alphaFromPoint(point: point) > 32
}
private func alphaFromPoint(point: CGPoint) -> UInt8 {
var pixel: [UInt8] = [0, 0, 0, 0]
let colorSpace = CGColorSpaceCreateDeviceRGB();
let alphaInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue)
if let context = CGContext(data: &pixel,
width: 1,
height: 1,
bitsPerComponent: 8,
bytesPerRow: 4,
space: colorSpace,
bitmapInfo: alphaInfo.rawValue) {
context.translateBy(x: -point.x, y: -point.y)
self.layer.render(in: context)
}
return pixel[3]
}
}
Don't forget to change the custom class of ImageView to CustomImageView in Xcode in the identity inspector.
If you now tap on transparent areas, the view of the ViewController in the background gets the tap. If you tap on non-transparent areas our image view receives the tap.
Demo
Here is a short demo of the above code using the image and mask from the question:

Repeating an action forever with a global function

I have a rectangle that needs to be constantly moving up, but is also declared globally like so so that I can call it in multiple places:
var obstacle = SKNode!
override func didMoveToView {
obstacle = rectangle()
}
func rectangle() -> SKNode {
let rect = SKSpriteNode(imageNamed: "Rectangle#x2")
rect.size = CGSizeMake(30, 30)
rect.position = CGPointMake(210, -250)
rect.physicsBody?.categoryBitMask = PhysicsCatagory.littleRect
rect.physicsBody?.contactTestBitMask = PhysicsCatagory.bigRect
rect.physicsBody?.collisionBitMask = 0
rect.physicsBody = SKPhysicsBody(rectangleOfSize: rect.size)
rect.physicsBody?.dynamic = true
rect.physicsBody?.affectedByGravity = false
rect.runAction(
SKAction.moveByX(0, y: 1200,
duration: NSTimeInterval(6.5)))
addChild(rect)
return rect
}
When I attempt to run it as an action repeating forever like so, i get the error "cannot convert value of type SKNode to argument runBlock" :
runAction(SKAction.repeatActionForever(
SKAction.sequence([
SKAction.runBlock(rectangle),
SKAction.waitForDuration(4.0)])))
So is there a way to declare this sort of action for a function set up like this? Thank you in advance.
First of all, this var obstacle = SKNode! will produce an error. You should declare an implicitly unwrapped optional like this:
var obstacle:SKNode!
About the main question (without analyzing the logic of what code actually does,)...You are passing an instance of SKNode class to +runBlock: method (which accepts a closure), thus the error. To fix this, you have to pass a closure, like this:
override func didMoveToView(view: SKView) {
runAction(SKAction.repeatActionForever(
SKAction.sequence([
SKAction.runBlock({[unowned self] in self.rectangle()}),
SKAction.waitForDuration(4.0)])))
}

Swift coordinates of a pressed button

I'm working on a tic tac toe game (3x3), so I have 9 buttons and what I want to do is to get coordinates of a button which user had pressed and insert an image on the place of the button.
Example:
#IBOutlet weak var button1Outlet: UIButton!
#IBAction func button1Action(sender: AnyObject) {
let imageName = "IMG.png"
let image = UIImage(named: imageName)
let imageView = UIImageView(image: image!)
imageView.frame = CGRect(x: 0, y: 0, width: 90, height: 90)
view.addSubview(imageView)
}
I know I can get coordinates of the button using button1Outlet.center , but I need something like this.center to avoid hardcoding it for every button.
You can cast the sender to a UIButton which you then can access any frame related methods on.
(sender as UIButton).center
When a button is clicked it is passed as a parameter to the function. Just use the sender.
// Change sender to a UIButton type
#IBAction func button1Action(sender: UIButton) {
// ... Setup your views
// Set same frames
imageView.frame = sender.frame
view.addSubview(imageView)
// If you want to remove the button
sender.removeFromSuperview()
}