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.
Related
I need to be able to interact with a representation of a cilinder that has many different parts in it. When the users taps over on of the small rectangles, I need to display a popover related to the specific piece (form).
The next image demonstrates a realistic 3d approach. But, I repeat, I need to solve the problem, the 3d is NOT required (would be really cool though). A representation that complies the functional needs will suffice.
The info about the parts to make the drawing comes from an API (size, position, etc)
I dont need it to be realistic really. The simplest aproximation would be to show a cilinder in a 2d representation, like a rectangle made out of interactable small rectangles.
So, as I mentioned, I think there are (as I see it) two opposite approaches: Realistic or Simplified
Is there a way to achieve a nice solution in the middle? What libraries, components, frameworks that I should look into?
My research has led me to SceneKit, but I still dont know if I will be able to interact with it. Interaction is a very important part as I need to display a popover when the user taps on any small rectangle over the cylinder.
Thanks
You don't need any special frameworks to achieve an interaction like this. This effect can be achieved with standard UIKit and UIView and a little trigonometry. You can actually draw exactly your example image using 2D math and drawing. My answer is not an exact formula but involves thinking about how the shapes are defined and break the problem down into manageable steps.
A cylinder can be defined by two offset circles representing the end pieces, connected at their radii. I will use an orthographic projection meaning the cylinder doesn't appear smaller as the depth extends into the background (but you could adapt to perspective if needed). You could draw this with CoreGraphics in a UIView drawRect.
A square slice represents an angle piece of the circle, offset by an amount smaller than the length of the cylinder, but in the same direction, as in the following diagram (sorry for imprecise drawing).
This square slice you are interested in is the area outlined in solid red, outside the radius of the first circle, and inside the radius of the imaginary second circle (which is just offset from the first circle by whatever length you want the slice).
To draw this area you simply need to draw a path of the outline of each arc and connect the endpoints.
To check if a touch is inside one of these square slices:
Check if the touch point is between angle a from the origin at a.
Check if the touch point is outside the radius of the inside circle.
Check if the touch point is inside the radius of the outside circle. (Note what this means if the circles are more than a radius apart.)
To find a point to display the popover you could average the end points on the slice or find the middle angle between the two edges and offset by half the distance.
Theoretically, doing this in Scene Kit with either SpriteKit or UIKit Popovers is ideal.
However Scene Kit (and Sprite Kit) seem to be in a state of flux wherein nobody from Apple is communicating with users about the raft of issues folks are currently having with both. From relatively stable and performant Sprite Kit in iOS 8.4 to a lot of lost performance in iOS 9 seems common. Scene Kit simply doesn't seem finished, and the documentation and community are both nearly non-existent as a result.
That being said... the theory is this:
Material IDs are what's used in traditional 3D apps to define areas of an object that have different materials. Somehow these Material IDs are called "elements" in SceneKit. I haven't been able to find much more about this.
It should be possible to detect the "element" that's underneath a touch on an object, and respond accordingly. You should even be able to change the state/nature of the material on that element to indicate it's the currently selected.
When wanting a smooth, well rounded cylinder as per your example, start with a cylinder that's made of only enough segments to describe/define the material IDs you need for your "rectangular" sections to be touched.
Later you can add a smoothing operation to the cylinder to make it round, and all the extra smoothing geometry in each quadrant of unique material ID should be responsive, regardless of how you add this extra detail to smooth the presentation of the cylinder.
Idea for the "Simplified" version:
if this representation is okey, you can use a UICollectionView.
Each cell can have a defined size thanks to
collectionView:layout:sizeForItemAtIndexPath:
Then each cell of the collection could be a small rectangle representing a
touchable part of the cylinder.
and using
collectionView:(UICollectionView *)collectionView
didSelectItemAtIndexPath:(NSIndexPath *)indexPath
To get the touch.
This will help you to display the popover at the right place:
CGRect rect = [collectionView layoutAttributesForItemAtIndexPath:indexPath].frame;
Finally, you can choose the appropriate popover (if the app has to work on iPhone) here:
https://www.cocoacontrols.com/search?q=popover
Not perfect, but i think this is efficient!
Yes, SceneKit.
When user perform a touch event, that mean you knew the 2D coordinate on screen, so your only decision is to popover a view or not, even a 3D model is not exist.
First, we can logically split the requirement into two pieces, determine the touching segment, showing right "color" in each segment.
I think the use of 3D model is to determine which piece of data to show in your case if I don't get you wrong. In that case, the SCNView's hit test method will do most of work for you. What you should do is to perform a hit test, take out the hit node and the hit's local 3D coordinate of this node, you can then calculate which segment is hit by this touch and do the decision.
Now how to draw the surface of the cylinder would be the only left question, right? There are various ways to do, for example simply paint each image you need and programmatically and attach it to the cylinder's material or have your image files on disk and use as material for the cylinder ...
I think the problem would be basically solved.
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
I have UISlider with min and max track colors. I want to draw vertical line (or image) in the middle of the slider track. This line must be there the whole time, only color on top of it will be different (min / max track color).
If I put UIImage on the Slider background, tracks covers it. If I set clear color for tracks, I can see line, but no colors from tracks (obviously). Is there any simple way, how to do this, or I have to override drawRect method for Slider ?
Something like on the slider in image
Note that the sort of "ultimate" solution to this type of project is...
http://www.paintcodeapp.com
As #BradAllred pointed out, the fundamental problem here is: you are trying to avoid drawRect.
Realistically, you can only achieve your goal here by subclassing UIControl or slider.
As others have mentioned fortunately there are many great tutorials, etc, on doing this -- and it's not hard.
Once again paintcodeapp.com is kind of an "incredible secret" to making iOS apps, enjoy.
add an UIView that is 1 pixel wide and has the height of the Slider and put it over the slider.
Create a CALayer, draw it, (even add an image to it), then add that layer as a sublayer to the Slider's layer.contents.
I've been trying to find a way to solve this problem, and haven't been able to find anything useful, so forgive me if this is a duplicate of something I couldn't find.
I have, essentially, a large complicated image in the style of a stained glass window in a scroll view so that I can pan and zoom around it. Each of the individual segments of the window has some information associated with it. What I need to be able to do is tap on any of the segments and determine which segment was tapped so that I can display the information. What I'm not sure of is how to do the mapping between touch points and segments. Most of the segments aren't even regular polygon shapes let alone orthogonal squares, so I can't think of a straightforward way to determine which segment I've tapped.
If anybody has any ideas as to how I might go about implementing this, it would be most appreciated!
Cheers
Put each individual segment in a different layer. Now you can do hit-testing on what layer was tapped. Your test must be designed so that if a layer was tapped but on a transparent area (i.e. outside its segment), your test will fall through to the next layer behind it. Thus the test will succeed if and when you discover a layer's non-transparent region under the tap. Since it is one segment per layer, the segment is the one corresponding to that layer.
I'm working on an app that lets the user stack graphics on top of each other.
The graphics are instantiated as a UIImageView, and is transparent outside of the actual graphic. I'm also using pan gestures to let the user drag them around the screen.
So when you have a bunch of graphics of different sizes and shapes on top of one another, you may have the illusion that you are touching a sub-indexed view, but you're actually touching the top one because some transparent part of it its hovering over your touch point.
I was wondering if anyone had ideas on how we could accomplish ONLY listening to the pan gesture on the solid part of the imageview. Or something that would tighten up the user experience so that whatever they touched was what they select. Thanks
Create your own subclass of UIImageView. In your subclass, override the pointInside:withEvent: method to return NO if the point is in a transparent part of the image.
Of course, you need to determine if a point is in a transparent part. :)
If you happen to have a CGPath or UIBezierPath that outlines the opaque parts of your image, you can do it easily using CGPathContainsPoint or -[UIBezierPath containsPoint:].
If you don't have a handy path, you will have to examine the image's pixel data. There are many answers on stackoverflow.com already that explain how to do that. Search for get pixel CGImage or get pixel UIImage.