I am working on a SwiftUI-based application that utilizes GCVirtualController to provide basic on-screen controls. While getting it set up has been relatively simple, I'm not particularly happy with layout and appearance of the controls themselves -- especially in portrait mode where the layout is completely broken.
Ideally what I'd like to do is to move the thumbsticks closer together and otherwise alter their appearance (such as make them smaller and/or add some more transparency).
Apple has some pretty sparse documentation on GCVirtualController and I don't see many examples of this API's use just looking around on GitHub. What I can find is it should be possible to customize the appearance of controller elements via the ElementConfiguration object. Among other things, this allows for a UIBezierPath property which seems like exactly what I would need. The problem is if I use code like this:
virtualController.updateConfiguration(forElement: GCInputLeftThumbstick, configuration: { _ in
var c = GCVirtualController.ElementConfiguration()
c.path = UIBezierPath(rect: CGRect(x: 0, y: 0, width: 20, height: 20))
return c
})
It doesn't seem to actually do anything to change the thumbstick appearance at all. (If I change other properties like "c.isHidden = true" that does work so I do know something is wired up here behind the scenes.)
For the sake of experimentation I tried applying this same code to GCInputButtonA and it did change the inner-button appearance -- so that's something, but it's not really what I want either. Maybe I'm just running into some undocumented limitations with this API.
Then there's the other problem -- even if I am able to somehow customize the appearance of the thumbsticks, I still do not see any obvious way to alter their positioning on the screen. This is important because right now GCVirtualController is broken on portrait mode; the left analog thumbstick is completely missing so I'd like to move things closer together while in portrait mode.
Hopefully someone who's more familiar with this may have some ideas what I'm getting wrong here. I'd prefer to not have to use 3rd party virtual controller APIs if possible, but I have a distinct feeling that's the direction I'll need to go.
I too am excited! :)
I watched the video and they just outright changed the "method/func/api"
Here is what I came up with and an example.
What I also found out is, I am pretty sure you can put any symbol like say size 500x500, since you need a UIBezierPath, the api just scales the bounds to fit in the button (but you have to center). Without further a do...
let virtualConfiguration = GCVirtualController.Configuration()
virtualConfiguration.elements = [GCInputLeftThumbstick,
GCInputRightThumbstick,
GCInputButtonA,
GCInputButtonB,
GCInputButtonX]
virtualController = GCVirtualController(configuration: virtualConfiguration)
virtualController!.updateConfiguration(forElement: GCInputButtonX, configuration: { _ in
let starPath = GCVirtualController.ElementConfiguration()
let offX = -25.0
let offY = -26.0
starPath.path = UIBezierPath()
starPath.path!.move(to: CGPoint(x: 25+offX, y: 1+offY))
starPath.path!.addLine(to: CGPoint(x: 33.82+offX, y: 13.86+offY))
starPath.path!.addLine(to: CGPoint(x: 48.78+offX, y: 18.27+offY))
starPath.path!.addLine(to: CGPoint(x: 39.27+offX, y: 30.64+offY))
starPath.path!.addLine(to: CGPoint(x: 39.69+offX, y: 46.23+offY))
starPath.path!.addLine(to: CGPoint(x: 25+offX, y: 41+offY))
starPath.path!.addLine(to: CGPoint(x: 10.31+offX, y: 46.23+offY))
starPath.path!.addLine(to: CGPoint(x: 10.73+offX, y: 30.64+offY))
starPath.path!.addLine(to: CGPoint(x: 1.22+offX, y: 18.27+offY))
starPath.path!.addLine(to: CGPoint(x: 16.18+offX, y: 13.86+offY))
starPath.path!.close()
UIColor.gray.setFill()
starPath.path!.fill()
return starPath
})
// Connect to the virtual controller if no physical controllers are available.
if GCController.controllers().isEmpty {
virtualController?.connect()
}
Cheers!!
Related
I have a situation where I have to reposition the map box compass view to a different location. The compassView is now rotating when I rotate the map taking some other point as its(compassView) axis and gives me a weird outcome. Screenshot is attached, the black mapBox default compass icon is rotating, refer the screenshots. Is this a MapBox sdk bug? if so, are there any work around? And tweaks? I am confused. Expert advices needed. Thanks in advance.
Hey I found another method where I will get the same result of the compass in the map box. I've placed a button and then
func mapViewRegionIsChanging(_ mapView: MGLMapView) {
compassViewUpdate(direction: Double(bearing))
inside compassViewUpdate method
func compassViewUpdate(direction:Double) {
self.compassButton.transform = CGAffineTransform(rotationAngle: CGFloat(-direction.degreesToRadians))
}
#Sarang here is a work around. Happy coding.
A built-in approach that's available in the Mapbox iOS SDK. Using these methods
to move the compass will avoid the distortion caused by .frame and .center
changes.
Generally Changing the CompassView Location
mapView.compassViewPosition = .bottomLeft
mapView.compassViewPosition = .bottomRight
mapView.compassViewPosition = .topLeft
mapView.compassViewPosition = .topRight
Use compassViewPosition to get the compass generally where you want it on the
screen.
Fine Tuning the CompassView Location
mapView.compassViewMargins = CGPoint(x: 64, y: 16)
Default x, y = 8, 8
The margin is to the outside/along the border of the view. So for .bottomLeft
the compass view will move 56 right and 8 up versus .topRight where the
compass will move 56 left and 8 down.
try changing the compassView.center instead of changing its origin or frame. I am guessing it has approximately 40 pixels width x height, and I changed to bottom left using the following:
let centerCalc = CGRect(x: 5, y: frame.height - 45, width: 40, height: 40)
compassView.center = CGPoint(x: centerCalc.midX, y: centerCalc.midY)
It should work!
The company that I work for has both an iOS and a tvOS version of an app. The app draws a script at the bottom for the user to keep up during a workout, like so:
This has worked fine for more than a year in any app targeted for iOS. However, when targeting tvOS, with the same drawing code, instead of drawing the way I would expect, the tvOS view has what I'll call "traces" left after drawing each line, as opposed to just having the one line for the current time.
The drawing code is as follows:
func addIndicatorLine(_ context:CGContext?, rect: CGRect, start: CGPoint, color: UIColor = UIColor.white) {
context!.translateBy(x: rect.origin.x, y: rect.height)
context!.scaleBy(x: 1.0, y: -1.0)
context!.setLineWidth(1.0)
context!.setStrokeColor(color.cgColor)
context!.move(to: CGPoint(x: start.x, y: 0))
context!.addLine(to: CGPoint(x: start.x, y: rect.size.height - 5))
context!.strokePath()
context!.scaleBy(x: 1.0, y: -1.0)
context!.translateBy(x: -rect.origin.x, y: -rect.height)
}
Am I missing something obvious, or is there something different I have to do for the tvOS version of the drawing code?
So after a couple of days of trying to figure this out, thanks to one answer (https://stackoverflow.com/a/43898524/2820553) that pointed out a line in the docs to me, I needed to give my view a background color, otherwise drawing errors may occur:
If the view’s opaque property is also set to YES, the backgroundColor property of the view must not be nil or drawing errors may occur.
Simply setting the view's background to a clear color solved this issue. Amazing that it didn't manifest in iOS but did in tvOS. Hope this helps anyone else looking for something similar.
First off, I want to preface by saying i know this question has a VERY small scope, specific to one pod that many people may have never used or even heard about. BUT if anyone has ever run into the same issue as me, or would like to do a little digging and help me out, some insight would be greatly and truly appreciated.
Hokusai is a Pod made by ytakzk that mirrors Skype's action sheet, bouncing up from the bottom of the page.
I have been looking over the code trying to make the necessary changes to get it to be pretty much the opposite when it comes to the y axis, bouncing down from the top and then receding back to the top when dismissed.
I am not familiar AT ALL with CGRects (currently learning!) so it is really difficult to even understand what to manipulate and where.
Thanks for reading, hopefully someone will be able to help me out, and it is one of the most popular pods, so if you do create a viable solution, i'm sure you will end up helping hundreds of others as well!
I feel like its somewhere within this method here, so I'm including it, although This is a link to the .swift file on GitHub if you think it may be something else.
func updatePath() {
let width = CGRectGetWidth(shapeLayer.bounds)
let height = CGRectGetHeight(shapeLayer.bounds)
let path = UIBezierPath()
path.moveToPoint(CGPoint(x: 0, y: 0))
path.addQuadCurveToPoint(CGPoint(x: width, y: 0),
controlPoint:CGPoint(x: width * 0.5, y: 0 + bendableOffset.vertical))
path.addQuadCurveToPoint(CGPoint(x: width, y: height + 100.0),
controlPoint:CGPoint(x: width + bendableOffset.horizontal, y: height * 0.5))
path.addQuadCurveToPoint(CGPoint(x: 0, y: height + 100.0),
controlPoint: CGPoint(x: width * 0.5, y: height + 100.0))
path.addQuadCurveToPoint(CGPoint(x: 0, y: 0),
controlPoint: CGPoint(x: bendableOffset.horizontal, y: height * 0.5))
path.closePath()
shapeLayer.path = path.CGPath
}
Edit: It is definitely (im pretty sure) going to take more than just fiddling with this one method. I've tried everything I can think of, I need some new eyes!
Thanks you so much in advance - Please Help!
-Dan
I want to use Xcode UI tests with the Fastlane Snapshot to make screenshots of the Cordova app. Basically, as my entire app is just a web view, all the Xcode UI test helper methods become irrelevant, and I just want to tap on specific points, e.g. tap(x: 10, y: 10) should produce a tap at the point {10px; 10px}.
That's probably very simple, but I can't figure out how to do it.
Thanks.
You can tap a specific point with the XCUICoordinate API. Unfortunately you can't just say "tap 10,10" referencing a pixel coordinate. You will need to create the coordinate with a relative offset to an actual view.
We can use the mentioned web view to interact with the relative coordinate.
let app = XCUIApplication()
let webView = app.webViews.element
let coordinate = webView.coordinateWithNormalizedOffset(CGVector(dx: 10, dy: 10))
coordinate.tap()
Side note, but have you tried interacting with the web view directly? I've had a lot of success using app.links["Link title"].tap() or app.staticTexts["A different link title"].tap(). Here's a demo app I put together demonstrating interacting with a web view.
Update: As Michal W. pointed out in the comments, you can now tap a coordinate directly, without worrying about normalizing the offset.
let normalized = webView.coordinate(withNormalizedOffset: CGVector(dx: 0, dy: 0))
let coordinate = normalized.withOffset(CGVector(dx: 10, dy: 10))
coordinate.tap()
Notice that I pass 0,0 to the normalized vector and then the actual point, 10,10, to the second call.
#joe To go a little further off of Joe Masilotti's approach I put mine in an extensionand gave prepositional phrases to the global and local params.
func tapCoordinate(at xCoordinate: Double, and yCoordinate: Double) {
let normalized = app.coordinate(withNormalizedOffset: CGVector(dx: 0, dy: 0))
let coordinate = normalized.withOffset(CGVector(dx: xCoordinate, dy: yCoordinate))
coordinate.tap()
}
By giving the global an identifiable name I can easily understand the instance for example:
tapCoordinate(at x: 100, and y: 200)
I found Laser's answer to work fine with Xcode 11, but made a few tweaks to easily integrate it into my testing.
extension XCUIApplication {
func tapCoordinate(at point: CGPoint) {
let normalized = coordinate(withNormalizedOffset: .zero)
let offset = CGVector(dx: point.x, dy: point.y)
let coordinate = normalized.withOffset(offset)
coordinate.tap()
}
}
Now, when I need to tap on a given location, I just provide a CGPoint and call this against my XCUIApplication like so:
let point = CGPoint(x: xCoord, y: yCoord)
app.tapCoordinate(at: point)
<something>.coordinate(withNormalizedOffset: CGVector.zero).withOffset(CGVector(dx:10,dy:60)).tap()
Pass .zero to the normalized vector and then the actual point (10,60)
Is there a general method to fill an area in Swift.
I have various shape in my UIView, lines, ellipses, curves … etc.
Those shapes delimit a certain number of areas.
I want to fill one area in a color (uiColorOne) and another area in another color (uiColorTwo).
Is that possible? If YES, how can I do that?
import UIKit
override func drawRect(rect: CGRect)
{
let path = UIBezierPath()
path.moveToPoint(CGPoint(x: 200, y: 200))
path.addLineToPoint(CGPoint(x: 200, y: 300))
path.addLineToPoint(CGPoint(x: 300, y: 200))
path.closePath()
UIColor.redColor().set()
path.lineWidth = 1
path.fill()
}
You will need to override drawRect in order to start making some geometric shaped items in your code (ONLY OVERRIDE AND NEVER CALL IT). The code snippet creates an element based on points (careful not pixels) and fills it with the redColor.You will have to use UIBezierPath most of the times you are playing with elements and so I suggest you have a really good look at it from apple documentation here, since it has some awesome methods that will help you out with your every day struggle : UIBezierPath