I am trying to create a progress circle for the Apple Watch version of my app. I know that we aren't able to use UIViews (which would make things so much easier!) so I am looking for alternatives.
Basically, I would like to create one of these prototypes:
The way I was hoping to do things was to add the background layers as a normal WKInterfaceImage and then the progress arrow/line on top as a WKInterfaceImage that rotates around the circle based on the percentage calculated.
I have the percentage calculated so basically, what I am looking for is some help with the math code for rotating the arrow.
Does anyone know if this is possible, and could anyone help me out if so? I'm not trying to update the circle while the app is running; it just needs to update when the Watch App launches to correspond with the iOS version.
Thanks!
As of watchOS 3 it is possible to use SpriteKit.
SKShapeNode can draw shapes, so it is possible to create radial rings.
Add a WKInterfaceSKScene to your interface controller in storyboard and hook it up through an outlet.
Set it up in the awake method of the interface controller
override func awake(withContext context: Any?) {
super.awake(withContext: context)
scene = SKScene(size: CGSize(width: 100, height: 100))
scene.scaleMode = .aspectFit
interfaceScene.presentScene(scene)
}
Create a shape node and add it to the scene
let fraction: CGFloat = 0.75
let path = UIBezierPath(arcCenter: .zero,
radius: 50,
startAngle: 0,
endAngle: 2 * .pi * fraction,
clockwise: true).cgPath
let shapeNode = SKShapeNode(path: path)
shapeNode.strokeColor = .blue
shapeNode.fillColor = .clear
shapeNode.lineWidth = 4
shapeNode.lineCap = .round
shapeNode.position = CGPoint(x: scene.size.width / 2, y: scene.size.height / 2)
scene.addChild(shapeNode)
Another solution is to create 100 picture, for each number you have a frame. So, doing that, you can show an animation using 'startAnimatingWithImagesInRange:duration:repeatCount' method.
The problem is that it's hard to customise each frame. Somebody thought about this issue and created a generator. You can find it by this name:
Radial Bar Chart Generator
This link should help you to customise the frames: http://hmaidasani.github.io/RadialChartImageGenerator/
Also you have same samples on git link.
For 100 frames with a single arc frame you get around 1,8 MB on disk.
Most of what is available on iOS is not present (yet) in WatchKit. In particular, several of the things you want to do are almost impossible. (Glimmer of hope in a moment). In particular, you cannot rotate an image. Or rather, you can rotate an image, but you have to do it on the phone and then pass that image up to the watch at runtime. Also, you cannot easily composite images - however, there is a way to do it.
One way would be to construct the entire rotated, composited image the way you want it on the phone and pass the final data up to the button using [WKInterfaceButton setBackgroundImage:]. Unfortunately, you will likely find this to be slow in the simulator, and most likely it will work poorly on the actual watch. Hard to know for sure because we don't have one, but this is sending the image on the fly over Bluetooth. So you won't get smooth animation or good response times.
A better way is to hack your way to it on the watch. This relies on two tricks: one, layering groups together with background images. Two, using -[WKInterfaceImage startAnimatingWithImagesInRange:duration:repeatCount:].
For the first trick, drop a Group into your layout, then put another group inside it, then (possibly) a button inside that. Then use -[WKInterfaceGroup setBackgroundImage:] and the images will composite together. Make sure you use proper transparency, etc.
For the second trick, refer to the official documentation - essentially, you will need a series of images, one for each possible rotation value, as erdekhayser said. Now, this may seem egregious (it is) and possibly impractical (it is not). This is actually how Apple recommends creating spinners and the like - at least for now. And, yes, that may mean generating 360 different images, although because of the small screen, my advice is to go every 3-5 degrees or so (nobody will be able to tell the difference).
Hope this helps.
Nobody posts code??? Here it is! enjoy:
1) Create the images here: http://hmaidasani.github.io/RadialChartImageGenerator/
2) Drag n Drop a picker into your View Controller, and link it to some viewController.swift file. Set the Picker style to "Sequence" on the menu that appears on the right.
3) Add this code to the viewController.swift , and connect the picker to the IBOutlet and the IBAction:
import WatchKit
import Foundation
class PickerViewController: WKInterfaceController {
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
}
#IBOutlet var itemPicker: WKInterfacePicker!
override func willActivate() {
super.willActivate()
let pickerItems: [WKPickerItem] = (0...100).map {
let pickerItem = WKPickerItem()
pickerItem.contentImage = WKImage(imageName: "singleArc\($0).png")
return pickerItem
}
itemPicker.setItems(pickerItems)
}
#IBAction func pickerSelectedItemChanged(value: Int) {
NSLog("Sequence Picker: \(value) selected.")
}
override func didDeactivate() {
super.didDeactivate()
}
}
WKInterface classes are not able to be subclassed. Therefore, a custom control is not possible.
Also, animation is limited. In order to create an animation, you must store every single frame as an image. Then, you can have an image view in your WatchKit app that cycles through these images.
Store the images in the Images.xcassets folder in the watch target, and try to mess around with the changing the frame based on the percentage the activity is finished.
One extra note: having 100 images would not be efficient, as each WatchKit app has only a limited amount of space on the watch it can take up (I believe it is 20MB, but I am not sure). Maybe have an image for every 5%.
No, there is not possible to creating this custom circle on watch kit, because UIView doesn't work on watch kit.
there is only solution of your problem is you have to put 100 images of each frames... and make sure that size of images is lesser than 20 MB. because the size of watch application is up to 20 MB
Related
I am close to completing my first project in SceneKit but I'm struggling with the last few steps. It is probably easiest to explain my progress by sharing a short screen capture video of the Xcode Simulator displaying my current scene.
As you can see by the screen capture my project is composed of three elements (this is all done in code, I do not import any external assets):
outside box (defined via six SCNBox objects per corner)
inside sun (defined via a SCNTube object for the circle and UIBezierPath objects per "ray")
position of camera
Based on feedback I have committed the code to GitHub.
Right now the camera is allowed to rotate as seen in the screen capture but the centre of rotation of the camera and of the objects doesn't align so it appears to spin off-axis.
Here's where I want to get to:
correct camera position so that the combined box & sun is positioned directly in front of the camera, filling the screen
maintain the sun's position as being fixed (already done I guess)
allow the box to rotate freely in x, y & z around the sun based on touch input - so the user can "flick" the box and watch it flip and spin around the sun
The code structure is straight forward:
class GameViewController: UIViewController {
var gameView: SCNView!
var gameScene: SCNScene!
var cameraNode: SCNNode!
var targetCreationTime: TimeInterval = 0
override func viewDidLoad() {
super.viewDidLoad()
initView()
initScene() // createSun() and createCube() called here
initCamera()
}
And with respect to the camera position:
func initCamera() {
let camera = SCNCamera()
cameraNode = SCNNode()
cameraNode.camera = camera
cameraNode.position = SCNVector3(x: 0, y: 0, z: 0)
cameraNode.rotation = SCNVector4Make(1, 0, 0, .pi/2)
}
But what I've found is that despite playing around with the random cameraNode.position and cameraNode.rotation values the camera view doesn't seem to change.
My questions - any help will be greatly appreciated:
advice on repositioning the camera (what am I doing wrong?!) - once it's in the right place I can easily set "gameView.allowsCameraControl = false"
advice on how to enable the box to spin about its axis around the sun (while the sun remains fixed)
stretch goal! Any kind of general "check out this tutorial" type info on materials and lighting, and embedding this view into a SwiftUI view
Thanks!
I decided to stop fighting the point of rotation and instead reposition the elements around this.
One interesting thing, which I’ve mentioned at the start of the createBox() func.
// originally debugCube & debugNode were used for debugging the pivot point of the box
// but I found have this large node helped to balance out the centre of mass
// set to fully transparent and added to boxNode as final step after all other transformations
If you comment out the lines 19-26 plus 117 you will completely remove debugNode. And funnily enough when you do that the box stops spinning correctly. But you add it back in and everything is fixed. I’m guessing it’s adding “mass” to the overall node and helping lock the point of rotation to the correct position. So in the end I just made it transparent!
The final (version 1.0) code is posted on GitHub at github.com/LedenMcLeden/logo
Use this answer in post for your camera: 57586437, remove camera rotation and take camera control off. Rotate your box with a simple (I'd do an x,y,z independent spin just to verify it) spin so that you'll know if your pivot point is correct. It should be ok by default and spin in place right in front of the camera, but depends on how you built your cube.
If you added the sun and stuff as a subnode of your box, then you're probably in decent shape and the pieces will rotate together.
If you want to do camera rotations similar to cameraControl, then you'll need to add a gesture recognizer and then you can start experimenting with it.
Hope that helps!
Forgive the abstract nature of this question, but I am working on a project to replicate the Dynamic Wallpaper that comes included with iOS. In effect, I am working to accomplish what is seen here, a set of circles that appear on-screen in random positions and float around randomly.
I am creating my circles as a subclassed UIView, as seen here:
class BokehCircle: UIView {
override func draw(_ rect: CGRect) {
// Set the path
let path = UIBezierPath(ovalIn: rect)
// Set the fill color
UIColor.purple.setFill()
// Fill
path.fill()
}
}
Then, I am adding my circles to my view at random points (code omitted for length, but this is functioning properly).
Where I am struggling now is to determine how to make the circles "float" about the screen, slowly, randomly, but never fully disappear off-screen. While I know how to animate positions using a CGAffineTransform, I'm hoping for some suggestions (you don't need to do my work for me) on where to turn. Using the term "float" is resulting in very skewed results and I'm not finding any help on what would accomplish this effect.
I am working on an app, with UISlider and need to make it circular.
Is it possible via some native code, or does a 3rd party library have to be integrated?
I want to fill color as user will slider over slider.
You can find 3rd party libraries for circular slider.
Take a look into these:
https://www.cocoacontrols.com/controls/uicircularslider
https://github.com/thomasfinch/Circular-UISlider
https://github.com/milianoo/CurvySlider
I came across this question in a seperate thread with multiple links and didn't like any of the answers offered so I thought I would add my own in what seemed to be the most relevant thread, on the off chance someone else finds their way here and likes the option I provide.
it is not elegant or simple but it is easy and I thought it would be a nice starting point for others to develop their own.
in this example the circular uiScroller is placed on a view (or HUD) that is presented over the top of an SKView. I then use a hitTest to transfer any touches from the gameScene to the UIView and use _touchesBegan and _touchesMoved to update a series of buttons that are hidden like so:
the arrows are all hidden but we use the frames of each box to register where the touch is
I made the arrows UIButtons so it was easier to use the storyboard but I think an imageView works fine as we are only checking the frame of the arrow inside _touchesBegan and then sending it to the function rather than collection a touch event from the arrow itself.
if upBtn.frame.contains(touch) {
upBtnPress() //if you made this a touch func like me a call to (self) as the sender may be required
}
the black circle itself remains visible so it looks like this is where your taps are going.
then finally you create multiple images like this:
any circular pattern for each location possible is appropriate
(I had a total of 16 points for mine)
and then under any press function you just change the image of the black circle to show the correct image based on where the touch location is and you're done!
circleDpad.image = UIImage(named: "up")
calculating the angle:
is quite very simple, there are 16 points on my dial so I need to convert this into a possible direction in 1...360
func angleChanged(angle: CGFloat) {
let valueReached = (angle / 16) * 360 // this gives you the angle in degrees, converting any point that's x/16 into x/360
let turret = checkBaseSelect() //this is my personal code
turret?.tAngle = CGFloat(valueReached) * (CGFloat.pi / 180) //this converts degrees to radians and applies it where I want it.
}
despite only having 16 points I found that even without applying animation it looks quite smooth. thanks for reading.
this is a gif of my circular slider in action
This is my first ever guide, if it needs more information (or should be somewhere else) please let me know.
I'm trying to use iPhone 6s front camera's flash. I searched online and on Apple Docs but didn't find a useful explanation for how to use it. I'm currently making a custom front flash camera but I'm trying to use the official one used by Apple's camera.
Here's my code for my custom flash: (It's basically a white UIView that shows for a second and I set the phone's brightness to 1)
var flashview: UIView!
in viewDidLoad
flashview = UIView(frame: CGRectMake(UIScreen.mainScreen().bounds.width/2, UIScreen.mainScreen().bounds.height/2, UIScreen.mainScreen().bounds.width, UIScreen.mainScreen().bounds.height))
flashview.center = CGPointMake(self.view.bounds.width/2, self.view.bounds.height/2)
flashview.backgroundColor = UIColor.whiteColor()
flashview.alpha = 0.0
flashview.layer.zPosition = 100
self.view.addSubview(flashview)
when photo is taken
UIScreen.mainScreen().brightness = CGFloat(1.0)
self.flashview.alpha = 1.0
sleep(1)
self.flashview.alpha = 0.9
sleep(1)
self.flashview.alpha = 0.0
UIScreen.mainScreen().brightness = CGFloat(0.5)
Is there any possible way to use the same flash used in the official Apple camera?
Any time you're tempted to use sleep(), slap yourself and realize you're Doing It Wrong™.
You really need to look into using animations. This uses nicely optimized (by the system) timings that won't block the main thread (the user interface among other things) by waiting for a forced sleep() to finish.
Your case is a little more complicated because I don't believe UIScreen's brightness is directly animatable. This means you'll need to create a custom animatable property (maybe call it "flashLevel" or something), which in turn updates UIScreen's brightness to match.
You should probably also note the user's brightness level first, animate to brightest second, then animate back to the user's level at the end of the "to maximum brightness" stage. I know it'd annoy me if an app screwed with my screen brightness and didn't put it back the way it was...
My best advice is to read the documentation to which I linked, try your best to get it working, then update your question to include the code you've tried and what's not working if you can't figure it out.
I'm doing my first iOs game using Sprite Kit and Swift.
I start positioning all my Sprites and labels like:
sprite.position = CGPoint(x: 0, y: 200)
when i run in a 4-inch device, it looks really good but when i run it in a 3.5 device the game looks incomplete.
Is there any good solutions to resize all the layers instead of redesign all my scenes?
If you didn't change the initial setting for your scene scaleMode, you should have it set to .aspectFill. This setting will size your scene keeping it's aspect ration but trying to fill your screen. It will chop part of it basically. There's little you can do except go for an .aspectFit setting. It will keep the aspectRation but will fit all your scene in the screen leaving you with black letter boxing bands.
Most people do not use .aspectFit but if you know how to resize the scene depending on the screen size, you can add the needed padding on either side to remove the black letter boxing. I created a framework that does that for you and also calculates your original anchor point for your scene so you loose nothing of your current coordinates implementation. All with 2 methods.. I highly suggest you take a look at it: SceneSizer
Just:
Download the ZIP file for the Repository
Open the "SceneSizer" sub-folder
Drag the SceneSizer.framework "lego block" in your project
Make sure that the Framework in Embedded and not just Linked
Import the Framework somewhere in your code import SceneSizer
And you're done, you can now call the sizer Class with: SceneSizer.calculateSceneSize(#initialSize: CGSize, desiredWidth: CGFloat, desiredHeight: CGFloat) -> CGSize
Documentation is in the folder for a clean and full use with a standard scene. Hope this helps!