Swift: Finding views in a superview (without a for loop) - swift

I'm using the following code to find a certain subview inside my view. It works fine, and the for loop runs only once because it only finds one subview that satisfies the where clause.
for view in boardView.subviews where (view as? PlayingCardView)?.position.location == source.
&& (view as! PlayingCardView).position.column == source.column
&& (view as! PlayingCardView).position.row >= source.row
However, it seems weird that I'm running a loop here when I'm not actually doing any...loop-y things (know what I mean)?
Is there some other way to "search" like this? Like a way to use subviews.index(of:) and use the conditions from the where clause, or something like that?
Also, I know that I could do the same code like this:
for view in boardView.subviews {
if let cardView = view as? PlayingCardView {
if cardView.position.location == source.location
&& (view as! PlayingCardView).position.column == source.column
&& (view as! PlayingCardView).position.row >= source.row {
// Do stuff
}
}
}
Is one of these ways computationally faster?

I believe you're looking for the filter method.
if let card = (boardView.subviews as? [PlayingCardView])?.filter({
return $0.position.location == source.location
&& $0.position.column == source.column
&& $0.position.row >= source.row
}).first {
// Do stuff
print(card)
}
Or if you want to find the first card that satisfies your arguments, you can use the first method.
if let card = (boardView.subviews as? [PlayingCardView])?.first(where: {
return $0.position.location == source.location
&& $0.position.column == source.column
&& $0.position.row >= source.row
}) {
// Do stuff
print(card)
}

first(where: ) will give you the first element in the array that satisfies a condition (assuming that you only want to do it on one element since it's not "loopy"):
let view = boardView.subviews.first {
guard let cardView = $0 as? PlayingCardView else { return false }
return cardView.position.location == source.location
&& cardView.position.column == source.column
&& cardView.position.row >= source.row
}
// do stuffs to `view`
It will stop as soon as a match is found so that's about as efficient as you can get. In reality though, it hardly matters as you tend to have only a small number of subviews. Otherwise the GPU will give out first from having to render all of them.

Related

How can i make a functionality that uses if statements to switch images based on a random result

I have this simple app that is a dice app that has two dice. I want to show a label "JACKPOT!" at alpha(transparency) 1.0 when the user has both dices land with the same number.
Example:
User rolls DiceOne && DiceOne label "jackpot!" changes alpha to 1.0
I tried using an if statement with an array of 5 images of each possible dice number ie: 1,2,3,4,5,6
The code that i wrote worked one day then the next day it no longer works.
let dicearray = [#imageLiteral(resourceName: "DiceOne"), #imageLiteral(resourceName: "DiceTwo"), #imageLiteral(resourceName: "DiceThree"), #imageLiteral(resourceName: "DiceFour"), #imageLiteral(resourceName: "DiceFive"), #imageLiteral(resourceName: "DiceSix")]`
func jackpotShows() {
if diceImageView1.image == dicearray[0] && diceImageView2.image == dicearray[0] {
jackpotLabel.alpha = 1.0
}
}
Change your if statement from
//This is wrong
if diceImageView1.image == dicearray[0] && diceImageView2.image == dicearray[0] {
jackpotLabel.alpha = 1.0
}
to
//This is right
if diceImageView1.image == diceImageView2.image {
jackpotLabel.alpha = 1.0
}
Or, alternatively, you could do a for loop:
//This is right
for image in diceArray {
if diceImageView1.image == image && diceImageView2.image == image {
jackpotLabel.alpha = 1.0
}
}

how to move view when keyboard appears on small devices only

I am working on iPhone application and I have multiple UITextFields for input.
the problem with small devices only like iPhone 5 , 6. when The keyboard appears , the bottom textFields hide.
it is working fine with big iPhone screen like XR , XS Max
how can I add condition that check if the bottom textfields Hidden or not ?
guard let keyboardReact = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue else {
return
}
let screen = view.frame.size.height - keyboardReact.height
let safeAreHeight = self.view.frame.height - self.topLayoutGuide.length - self.bottomLayoutGuide.length
if safeAreHeight + keyboardReact.height > view.frame.size.height {
if currentTappedTextField == phoneTextField || currentTappedTextField == employeeEmailTextField || currentTappedTextField == relationTextField {
if notification.name == UIResponder.keyboardWillShowNotification || notification.name == UIResponder.keyboardWillChangeFrameNotification{
view.frame.origin.y = -(keyboardReact.height)
} else {
view.frame.origin.y = 0
}
}
}
This is work with all screen sizes I want it work only when the keyboard hide the textFields
Now you can calculate your keyboard height and move your view accodingly
func liftViewUp(notification: NSNotification){
if let keyboardSize = notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? CGRect {
// manage your view accordingly here
if currentTappedTextField == phoneTextField || currentTappedTextField == employeeEmailTextField || currentTappedTextField == relationTextField {
if notification.name == UIResponder.keyboardWillShowNotification || notification.name == UIResponder.keyboardWillChangeFrameNotification{
let textFieldPosition = currentTappedTextField.frame.origin.y + currentTappedTextField.frame.size.height
// check if textfield will hide behind keyboard
if textFieldPosition > (view.frame.size.height - keyboardReact.height){
view.frame.origin.y = -(keyboardReact.height)
}else {
view.frame.origin.y = 0
}
} else {
view.frame.origin.y = 0
}
}
}
}
or You can try this third party library IQKeyboardManager
You may got answer here

iOS Sprites Contact Detection

I'm working on a platform game where the character can climb ladders. Since the total length of each ladder varies, I have several ladders stacked on top of each other in SceneEditor. Upon entering a ladder, the contact delegate works fine and my code allows the character to move up the ladder. The problem I'm having is that as soon as the character moves off the first ladder segment, the didEnd method fires even though the character has entered the next ladder segment. I've gotten around it by given the ladder segments different category masks. Is this the only way to do it?
try overlapping the ladders by 1 rung, and then set a count whenever didBegin contact is fired increase the value by 1 whenever didEnd is called decrease the value by 1. at the end of the didEnd func check if onladderCount == 0. if it is, trigger whatever code is supposed to fire when the player is not on a ladder.
this assumes that you have a ladder class, and will have to put a property in the ladder class for onLadder to ensure that the += 1 isn't called multiple times.
var onladderCount: Int = 0
func didBegin(_ contact: SKPhysicsContact) {
let contactAName = contact.bodyA.node?.name
let contactBName = contact.bodyB.node?.name
if (contactAName == "ladder") || (contactBName == "ladder") {
let ladder: Ladder? = (contact.bodyA.categoryBitMask == PhysicsCategory. ladder ? (contact.bodyA.node as? Ladder) : (contact.bodyB.node as? Ladder))
if !ladder.onLadder {
ladder.onLadder = true
onladderCount += 1
}
}
}
func didEnd(_ contact: SKPhysicsContact) {
let contactAName = contact.bodyA.node?.name
let contactBName = contact.bodyB.node?.name
if (contactAName == "ladder") || (contactBName == "ladder") {
let ladder: Ladder? = (contact.bodyA.categoryBitMask == PhysicsCategory. ladder ? (contact.bodyA.node as? Ladder) : (contact.bodyB.node as? Ladder))
if ladder.onLadder {
ladder.onLadder = false
onladderCount -= 1
}
}
if onladderCount == 0 {
//trigger whatever code happens when they leave a ladder
}
}

How to tap on a button inside a CollectionView cell which is not visible on screen

I am working on UITests using XCode. I have multiple CollectionView cells.
When I perform Count in the collectionView it shows the certain count.
I can able to access first two cells but coming to the 3rd cell as 3(depends on device size). It says that specific button I am looking for in 3rd Cell as exists.
But isHittable is false.
Is there any way I can tap on the button on the 3rd Cell.
I have tried using the extension for forceTapElement() which is available online, it didn’t help.
Extension Used:
extension XCUIElement{
func forceTapElement(){
if self.isHittable{
self.tap()
}else{
let coordinate: XCUICoordinate = self.coordinate(withNormalizedOffset: .zero)
coordinate.tap()
}
}
}
Tried to perform swipeUp() and access the button. it still shows isHittable as false
The only way I've found is to swipe up untile the isHittable will be true.
app.collectionViews.cells.staticTexts["TEST"].tap()
Thread.sleep(forTimeInterval: 3)
let collectionView = app.otherElements.collectionViews.element(boundBy: 0)
let testAds = collectionView.cells
let numberOfTestAds = testAds.count
if numberOfTestAds > 0 {
let tester = collectionView.cells.element(boundBy: 2).buttons["ABC"]
for _ in 0..<100 {
guard !tester.isHittable else {
break;
}
collectionView.swipeUp()
}
}
Please note that the swipeUp() method will only move few pixels. If you want to use more comprehensive methods you can get AutoMate library and try swipe(to:untilVisible:times:avoid:from:):
app.collectionViews.cells.staticTexts["TEST"].tap()
Thread.sleep(forTimeInterval: 3)
let collectionView = app.otherElements.collectionViews.element(boundBy: 0)
let testAds = collectionView.cells
let numberOfTestAds = testAds.count
if numberOfTestAds > 0 {
let tester = collectionView.cells.element(boundBy: 2).buttons["ABC"]
collectionView.swipe(to: .down, untilVisible: tester)
// or swipe max 100 times in case 10 times is not enough
// collectionView.swipe(to: .down, untilVisible: tester, times: 100)
}

Creating sound effects for bouncing ball in SpriteKit

My Swift game uses SpriteKit and SKPhysics to animate a small red ball that frequently collides with other physicsBodys over the course of the game. I want to create an natural sounding bounce sound effect whenever the ball collides. I currently have a rather disorderly method for doing this, and there's a significant lag noticeable while employing it that disappears when I disable sounds:
var bounceSoundPlayers = [AVAudioPlayer!]()
func buildBounceSound() {
let path = Bundle.main.path(forResource: "Bounce.mp3", ofType:nil)!
let url = URL(fileURLWithPath: path)
for _ in 0...2 {
do {
let sound = try AVAudioPlayer(contentsOf: url)
sound.prepareToPlay()
bounceSoundPlayers.append(sound)
} catch {
fatalError("couldn't load music file")
}
}
}
This creates three AVAudioPlayers with sound "Bounce.mp3" and runs the prepareToPlay() method on them. Then it stores them in an array called bounceSoundPlayers so I can use them later. Then I have a didBeginContact method that is called whenever anything collides with anything:
func didBegin(_ contact: SKPhysicsContact) {
if contact.collisionImpulse > 0.45 && defaults.bool(forKey: "SFX") {
if ((contact.bodyA.node!.name == "ball" && contact.bodyB.node!.name == "permeable platform") ||
(contact.bodyB.node!.name == "ball" && contact.bodyA.node!.name == "permeable platform") ||
(contact.bodyA.node!.name == "ball" && contact.bodyB.node!.name == "ring") ||
(contact.bodyB.node!.name == "ball" && contact.bodyA.node!.name == "ring"))
&&
ball.collidingWithPermeable {
playBounceSound(contact.collisionImpulse/8<=1 ? contact.collisionImpulse/8 : 1)
}
if (contact.bodyA.node!.name == "ball" && contact.bodyB.node!.name == "impermeable platform") ||
(contact.bodyB.node!.name == "ball" && contact.bodyA.node!.name == "impermeable platform") ||
(contact.bodyA.node!.name == "ball" && contact.bodyB.node!.name == "wall") ||
(contact.bodyB.node!.name == "ball" && contact.bodyA.node!.name == "wall") {
playBounceSound(contact.collisionImpulse/5<=1 ? contact.collisionImpulse/5 : 1)
}
}
}
Basically what this method does is it checks each collision, and if the ball collides with the right kind of platform or wall, it calls the method playBounceSound(volume: CGFloat) with a volume proportional to the force of the collision. So if the ball collides with a platform lightly, the volume is quieter than if it's a really strong collision. Here's the playBounceSound method:
var bouncePlayerIndex = Int(0)
func playBounceSound(_ volume: CGFloat) {
let previousIndex = bouncePlayerIndex - 1 >= 0 ? bouncePlayerIndex - 1 : 2
if bounceSoundPlayer[previousIndex].isPlaying {
if bounceSoundPlayer[previousIndex].currentTime > 0.1 {
bounceSoundPlayer[bouncePlayerIndex].volume = Float(volume)
bounceSoundPlayer[bouncePlayerIndex].play()
}
}
else {
bounceSoundPlayer[bouncePlayerIndex].volume = Float(volume)
bounceSoundPlayer[bouncePlayerIndex].play()
}
bouncePlayerIndex += 1
if bouncePlayerIndex > 2 {bouncePlayerIndex = 0}
}
Basically what this method does is it cycles through the three AVAudioPlayers we created earlier by using an index integer. Every time it's called, it tells the bounceSoundPlayer at bouncePlayerIndex to play() at the given volume. Then it increments bouncePlayerIndex so that next time it's called, it uses a different AVAudioPlayer. If bouncePlayerIndex > 2, it goes back to 0. Using three AVAudioPlayers allows me to play multiple bounces simultaneous — i.e. a bounce sound can begin before the last one ended, such as when the ball lands on a platform and bounces high, then lower and lower and more frequently, until it stops bouncing.
I'm sure there's a better way of doing this than with an array of AVAudioPlayers. Please help.
Swift 3
This is the best way I have found to play sounds. They can overlap and you can play as many as you want at the same time. Fast and simple.
1.) Make sure you import the framework
import AVFoundation
2.) Create the function
// my sound file is named shatter.mp3
func shatterSound() {
if let soundURL = Bundle.main.url(forResource: "shatter", withExtension: "mp3") {
var mySound: SystemSoundID = 0
AudioServicesCreateSystemSoundID(soundURL as CFURL, &mySound)
AudioServicesPlaySystemSound(mySound);
}
}
3.) Call the function where you need it with
shatterSound()