Struggling with referencing a variable (Swift) - ios

Background:
I'm trying to animate a wave using BAFluidView. My goal is pretty simple: start the animation when a button is tapped, stop it when it is tapped again. The pod (linked above) provides all of the code to manage this.
Let it be known, I'm new to this. Help me learn, please!
My struggle:
I need to create a view for this wave. I put it into a function called "WaveContainer." Here's what that looks like:
func WaveAnimation() {
let wave = BAFluidView(frame: self.view.frame, startElevation: 0.02)!
wave.maxAmplitude = 10
wave.minAmplitude = 8
wave.fillDuration = 50
wave.fill(to: 0.95)
wave.fillAutoReverse = false
wave.fillColor = UIColor.blue
waveView.addSubview(wave)
}
I then called this in the ViewDidAppear function. Of course, it works! I can see the wave, and it's waving. Nice.
Of course, I can't call the wave constant anywhere else, though! If I want to stop / start the wave on a button press, for example?
If I try to move the wave constant out of this function into ViewDidLoad or ViewDidAppear, I can't access the ...self.view.frame, and the wave won't show up on the screen.
My asks:
How should I structure this code so that I can reference the wave constant from several different functions?
How should I reference the view for the wave constant when I'm not within ViewDidLoad or some other view-based function?
Thank you SO MUCH for your help!
My Code:
import UIKit
import BAFluidView
class ViewController: UIViewController {
// OUTLETS:
#IBOutlet weak var waveView: UIView!
// ACTIONS:
#IBAction func WaveButton(_ sender: UIButton) {
// If the user taps this button and the waveHasStarted variable is equal to false, flip it to true.
if waveHasStarted == false {
print("Start the wave with a press.")
startWave = true
waveHasStarted = true
waveHasBeenStopped = false
} else {
print("Stop the wave with a press.")
startWave = false
waveHasStarted = false
waveHasBeenStopped = true
}
}
// VARIABLES:
var waveHasStarted = false
var startWave = false
var waveHasBeenStopped = true
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {}
// FUNCTIONS:
func WaveAnimation() {
let wave = BAFluidView(frame: self.view.frame, startElevation: 0.02)!
wave.maxAmplitude = 10
wave.minAmplitude = 8
wave.fillDuration = 50
wave.fill(to: 0.95)
wave.fillAutoReverse = false
wave.fillColor = UIColor.blue
// If the variable above has been flipped to "true," start the animation...
if startWave == true {
print("Start that wave animation")
wave.startAnimation()
} else {
// If not, stop it or do nothing.
print("Stop that wave animation")
wave.stopAnimation()
wave.keepStationary()
}
waveView.addSubview(wave)
}
}

Use lazy initialization and get the class level access,
lazy var fluidView: BAFluidView = {
let wave = BAFluidView(frame: self.view.frame, startElevation: 0.02)!
wave.maxAmplitude = 10
wave.minAmplitude = 8
wave.fillDuration = 50
wave.fill(to: 0.95)
wave.fillAutoReverse = false
wave.fillColor = UIColor.blue
return wave
}()
Now you can add, start and stop the animation anywhere as,
waveView.addSubview(fluidView)
fluidView.startAnimation()
fluidView.stopAnimation()

Related

Do a vertical Flip animation in Swift

I am currently trying to get a vertical flip animation for my card in my project's subview. I am using some dependencies (Shuffle Cocoapod), and I tried everything to attempt to get a flip animation from right to left of my card. It seems like I can change the background and other simple stuff of each single card, but nothing happens when trying to animate it. The goal would be to "show the back of the card" with another word in it (basically each card has a "front word" and a "back word". What am I getting wrong?
import UIKit
import Shuffle
class CardsViewController: UIViewController, SwipeCardStackDataSource, SwipeCardStackDelegate {
let cardStack = SwipeCardStack()
var actualIndex : Int = 0
var showingBack = false
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(cardStack)
cardStack.frame.size.height = 512
cardStack.frame.size.width = 384
cardStack.center.y = CGFloat(cardStack.frame.size.height/2)
cardStack.center.x = CGFloat(cardStack.frame.size.width/2)
cardStack.center = view.center
cardStack.dataSource = self
}
func card(fromImage word: String) -> SwipeCard {
let card = SwipeCard()
card.swipeDirections = [.left, .right]
for direction in card.swipeDirections {
card.setOverlay(CardOverlay(direction: direction), forDirection: direction)
}
card.content = CardContentView(withWord: word)
return card
}
func cardStack(_ cardStack: SwipeCardStack, cardForIndexAt index: Int) -> SwipeCard {
let theCard = card(fromImage: cardWords[actualIndex].word)
actualIndex = actualIndex + 1
return theCard
}
func numberOfCards(in cardStack: SwipeCardStack) -> Int {
return cardWords.count
}
let cardWords = [
DataOfWord(word: "comme", translation: "as"),
DataOfWord(word: "je", translation: "I"),
DataOfWord(word: "son", translation: "his"),
DataOfWord(word: "que", translation: "that")]
}
You can create a flip type of animation with the UIView's transition(with:duration:options:animations:completion:) method
Here's some code from one of my own apps where I flip the gameboard to display the level completion score on the "other side":
UIView.transition(with: gameBoardCollectionView, duration: 0.75, options: [.transitionFlipFromRight]) {
self.loadLevelEndArcadeView()
} completion: { (complete) in
self.animateLevelEndArcadeLabelViews()
}
So, inside the transition code block, you just load the views you want and when the animation completes, you'll be displaying something that represents the back side of a view. There are also other transition animations you can call upon if you want.

Change color to a shape added as subview

I created a UIView, called CircleView, that draws a circle. This was added with a click of a button and located with a UITapGestureRecognizer in another view, canvasView, as a subview.
This is the code for circle and it works:
#IBAction func tapCircle(_ sender: UITapGestureRecognizer) {
let tapPoint2 = sender.location(in: canvasView)
let shapeCircle = CircleView(origin: tapPoint2)
canvasView.addSubview(shapeCircle)
shapeCircle.tag = 300
}
#IBAction func circleDraw(_ sender: UIButton) {
canvasView.isUserInteractionEnabled = true
tapCircle.isEnabled = true
tapRect.isEnabled = false
tapGRQ.isEnabled = false
canvasView.setNeedsDisplay()
}
It's all okay but i'm wondering if it is possible to change the color of this shape with a click of a button. Thanks a lot.
Create global variables for arrays of shapes
var circleShapes = [UIView]()
var squareShapes = [UIView]()
...
then append new UIView to certain array after you create it
let shapeCircle = CircleView(origin: tapPoint2)
canvasView.addSubview(shapeCircle)
circleShapes.append(shapeCircle)
...
now just change backgroundColor for each view inside certain array
circleShapes.forEach { $0.backgroundColor = /* UIColor */ }
...
Or if your canvasView contains just shapes subviews, every shape has its own UIView subclass and you want to change colors in the same time, you can change backgroundColor depending on type of shape
canvasView.subviews.forEach {
switch $0 {
case is CircleView:
$0.backgroundColor = /* UIColor */
case is SquareView:
$0.backgroundColor = /* UIColor */
...
default:
continue
}
}

Subview is not displaying the drawing view

I am trying to draw an arc similar to this Draw segments from a circle or donut. If I add it on viewDidLoad then it works but when I am waiting for an API call and then trying to add in a reload Method subviews are not added. Any idea?
Thanks in advance!!!
override func reloadData() {
self.drawCircle()
}
func drawCircle() {
let maxArcAngle = (360 - spacingInDegree*totalLevelsRequired) / totalLevelsRequired
var previousStartAngle = 0
var previousEndAngle = 360-maxArcAngle
if let rating = userRating {
for _ in 1...totalLevelsRequired {
let vi = MView(frame: CGRect(x:16.0,y:12.0,width:40.0,height:40.0))
print(previousStartAngle)
print(previousEndAngle)
previousStartAngle = previousEndAngle - spacingInDegree
previousEndAngle = previousStartAngle - maxArcAngle
vi.backgroundColor = UIColor.clear
addSubview(vi)
}
}
}

Passing variable from UIViewController to SK Scene

I'm making an a game in XCode 7 using Swift 2. I have a variable that I want to pass from the start screen (which is an UIViewController) to the game scene (which is an SKScene). I want the player to select a character in a UIView and play with it in the SKScene. I also want the score that's in the SKScene to show in the game-over screen that's an UIView. I've seen tutorials for passing data between two UIViewControllers and between two SKScenes, but none of them work for this case.
How can I pass a variable from an UIViewController to a SKScene (and vice versa)?
I ran over this same problem yesterday.
Swift 5.2, xcode 12 and targeting iOS 14. Searched high and low. Eventually found the userData attribute of the SKScene.
Note: The app is based on SwiftUI and the SKScene is inside a UIViewRepresentable:
struct SpriteKitContainer: UIViewRepresentable {
typealias UIViewType = SKView
var skScene: SKScene!
#Binding var timerData : ModelProgressTimer
Not that I am passing a binding into the View - this contains a number of data items I wanted to make available to the scene.
I placed code in two places to populate skScene.userData:
func makeUIView(context: Context) -> SKView {
self.skScene.scaleMode = .resizeFill
self.skScene.backgroundColor = .green
debugPrint("setting user data")
self.skScene.userData = [:]
self.skScene.userData!["color"] = UIColor(timerData.flatColorTwo!)
self.skScene.userData!["running"] = timerData.isRunning
self.skScene.userData!["percent"] = timerData.percentComplete
let view = SKView(frame: .zero)
view.preferredFramesPerSecond = 60
// view.showsFPS = true
// view.showsNodeCount = true
view.backgroundColor = .clear
view.allowsTransparency = true
return view
}
func updateUIView(_ view: SKView, context: Context) {
self.skScene.userData = [:]
self.skScene.userData!["running"] = timerData.isRunning
self.skScene.userData!["color"] = UIColor(timerData.flatColorTwo!)
self.skScene.userData!["percent"] = timerData.percentComplete
view.presentScene(context.coordinator.scene)
}
Then, inside the skScene object, I am able to retrieve the userData values:
var item: SKShapeNode! = nil
override func sceneDidLoad() {
let scaleUp = SKAction.scale(by: 2.0, duration: 1.0)
let scaleDown = SKAction.scale(by: 0.5, duration: 1.0)
scaleUp.timingMode = .easeInEaseOut
scaleDown.timingMode = .easeInEaseOut
let sequence = SKAction.sequence([scaleUp,scaleDown])
actionRepeat = SKAction.repeatForever(sequence)
item = SKShapeNode(circleOfRadius: radius)
addChild(item)
}
override func didChangeSize(_ oldSize: CGSize) {
item.fillColor = self.userData!["color"] as! UIColor
item.strokeColor = .clear
let meRunning = self.userData!["running"] as! Bool
if meRunning {
item.run(actionRepeat)
} else {
item.removeAllActions()
}
}
This draws a circle that "pulses" if the "running" boolean is set in the userdata (a Bool value).
If you haven't already found the answer, I did find a way to do this. I was doing something very similar.
In my case I have a UIView that gets called as a pause menu from my scene. I have made the class and variable names a little more ambiguous so they can apply to your situation as well.
class MyView: UIView {
var scene: MyScene!
var button: UIButton!
init(frame: CGRect, scene: MyScene){
super.init(frame: frame)
self.scene = scene
//initialize the button
button.addGestureRecognizer(UITapGestureRecognizer(target: self, action: "buttonTapped"))
}
func buttonTapped(){
self.removeFromSuperview() // this removes my menu from the scene
scene.doTheThing() // this calls the method on my scene
}
Here are the relevant parts of my scene.
class MyScene: SKScene {
func doTheThing(){
//this is a function on the scene. You can pass any variable you want through the function.
}
}
In your situation, it sounds like the first screen is a UIView and the second screen is the SKScene.
You may want to make your SKScene first, pause it, and then add the UIView in front of the Scene. Once the character is selected, you can remove the UIView and add your character into the scene.
I hope that this answers your question. If it doesn't let me know.
From the level select scene:
let scene = GameScene(fileNamed:"GameScene")
scene?.userData = [:]
scene?.userData!["level"] = 21
self.view?.presentScene(scene!)
From within the game scene:
let level = self.userData!["level"] as! Int

textField prevents keyboard showing event

I found a calendarDatePicker here and I want to show that custom picker when user touched textField.
In my previous project I just created a default pickerView and then set it to textField's inputView and it works.
And here I've tried to do this again but THCalendarDatePicker is a viewController, not an inputView.
Here is the code:
class ViewController: UIViewController, THDatePickerDelegate {
#IBOutlet weak var textField: UITextField!
var datePicker:THDatePickerViewController?
var curDate: NSDate?
override func viewDidLoad() {
super.viewDidLoad()
self.curDate = NSDate()
datePicker = THDatePickerViewController.datePicker()
datePicker!.delegate = self
datePicker!.setAllowClearDate(false)
datePicker!.setClearAsToday(true)
datePicker!.setAutoCloseOnSelectDate(false)
datePicker!.setAllowSelectionOfSelectedDate(true)
datePicker!.setDisableHistorySelection(false)
datePicker!.setDisableFutureSelection(false)
//datePicker!.autoCloseCancelDelay = 5.0
datePicker!.selectedBackgroundColor = UIColor.blackColor()
datePicker!.currentDateColor = UIColor.yellowColor()
datePicker!.currentDateColorSelected = UIColor.yellowColor()
}
#IBAction func touchInInput(sender: AnyObject) {
datePicker!.date = curDate
datePicker!.setDateHasItemsCallback({(date:NSDate!) -> Bool in
let tmp = (arc4random() % 30) + 1
return tmp % 5 == 0
})
presentSemiViewController(datePicker!, withOptions: [
KNSemiModalOptionKeys.pushParentBack : NSNumber(bool: true),
KNSemiModalOptionKeys.animationDuration : NSNumber(float: 0.5),
KNSemiModalOptionKeys.shadowOpacity : NSNumber(float: 0.3)
])
sender.inputView == ??? // what I should code here?
}
func datePickerCancelPressed(datePicker: THDatePickerViewController!) {
self.dismissSemiModalView()
}
func datePickerDonePressed(datePicker: THDatePickerViewController!) {
self.dismissSemiModalView()
}
func datePicker(datePicker: THDatePickerViewController!, selectedDate: NSDate!) {
println(selectedDate)
}
}
Can someone help me?
My suggestions would be to
1 programmatically create a container view, populate the container with the view controller, and then try to add that as the inputView for your keyboard.
I have my doubts that would work but it's certainly wouldn't hurt to try.
2 perhaps reconsider your design (if it's your call) and use a pop over view instead that contains your date picker.
Either way let me know how it works out.
Cant you set the datePicker.view value as input view

Resources