Swift 2 - User's Current Location is inside MKPolygon - swift2

I am trying to check if the user's current location is inside the MKPolygon. I have created the following function but it returns false for all my test cases. Is there something I might be doing wrong?
func isCorrectRegion(coordinates: Array<JSON>, userLocation: CLLocationCoordinate2D) -> Bool {
var newCoordinates: [CLLocationCoordinate2D] = []
var mapPoints: [MKMapPoint] = []
for(a):(JSON) in coordinates {
let lat = a.arrayValue[1].double
let long = a.arrayValue[0].double
let location = CLLocationCoordinate2D(latitude: lat!, longitude: long!)
newCoordinates.append(location)
}
for b in newCoordinates {
let c: MKMapPoint = MKMapPointForCoordinate(b)
mapPoints.append(c)
}
let polygon: MKPolygon = MKPolygon(points: &mapPoints, count: mapPoints.count)
let polyRender: MKPolygonRenderer = MKPolygonRenderer(polygon: polygon)
polyRender.invalidatePath()
let target: MKMapPoint = MKMapPointForCoordinate(userLocation)
let cgTarget: CGPoint = CGPoint(x: target.x, y: target.y)
let isWithin = CGPathContainsPoint(polyRender.path, nil, cgTarget, false)
return isWithin
}

Finally figured it out after trying a few different things. Hope this helps others:
func isCorrectRegion(coordinates: Array<JSON>, userLocation: CLLocationCoordinate2D) -> Bool {
var newCoordinates: [CLLocationCoordinate2D] = []
var mapPoints: [MKMapPoint] = []
let mpr: CGMutablePathRef = CGPathCreateMutable()
for(a):(JSON) in coordinates {
let lat = a.arrayValue[1].double
let long = a.arrayValue[0].double
let location = CLLocationCoordinate2D(latitude: lat!, longitude: long!)
newCoordinates.append(location)
}
for b in newCoordinates {
let c: MKMapPoint = MKMapPointForCoordinate(b)
mapPoints.append(c)
}
for var p = 0; p<mapPoints.count; p++ {
if p == 0 {
CGPathMoveToPoint(mpr, nil, CGFloat(mapPoints[p].x), CGFloat(mapPoints[p].y))
} else {
CGPathAddLineToPoint(mpr, nil, CGFloat(mapPoints[p].x), CGFloat(mapPoints[p].y))
}
}
let target: MKMapPoint = MKMapPointForCoordinate(userLocation)
let cgTarget: CGPoint = CGPoint(x: target.x, y: target.y)
let isWithin = CGPathContainsPoint(mpr, nil, cgTarget, false)
return isWithin
}

Related

fatal : Array index out of range

Every child has a pick up point location consists of latitude and longitude that i fetch from sqlite database. I have childIdArray which is array of childID's. I want to calculate the estimate time(ETA) from current location of their respective drivers location i fetch from getCurrentLocationOfRespectiveDrivers() method with pickUp point of children that i fetch from sqlite database in form of array of latitude and longitude.
but i get error
fatal error: Array index out of range
in getCurrentLocationOfRespectiveDrivers() method.
Any help would be highly appreciated.. thanks in advance
var etaArray : [String] = []
var estimatedTime : String?
var latitudeArray : [Double] = []
var longitudeArray : [Double] = []
override func viewDidLoad() {
super.viewDidLoad()
let rs = ModelManager.sharedInstance.fetchingPickUpAnnotationsArray(ChildDetailsVC.parentID)
latitudeArray = rs.latPickUp
longitudeArray = rs.longPickUp
print("pickUpLatitudeArray = \(latitudeArray)")
print("pickUpLongitudeArray = \(longitudeArray)")
let result = ModelManager.sharedInstance.fetchingChildren( ChildDetailsVC.parentID)
self.childNameArray = result.childNames
self.childImageArray = result.childImages
self.childIDArray = result.chidIDs
self.childDriverArray = result.childDrivers
getCurrentLocationOfRespectiveDrivers()
for (var i = 0; i < childIDArray.count; i++)
{
etaArray.append("")
}
}
func getCurrentLocationOfRespectiveDrivers()
{
for( var i = 0 ; i < latitudeArray.count ; i++)
{
let url:NSURL = NSURL(string:"http://development.ssntpl.com/gogo_app/api.php?action=getDriverCurrentLocation")!
let request = NSMutableURLRequest(URL:url)
request.HTTPMethod = "POST"
let post:NSString = "child_id=\(childIDArray)"
request.HTTPBody = post.dataUsingEncoding(NSUTF8StringEncoding)
let task = NSURLSession.sharedSession().dataTaskWithRequest(request)
{
data, response, error in
if error != nil
{
print("error is \(error)")
return;
}
do
{
let myJSON = try NSJSONSerialization.JSONObjectWithData(data!, options: .MutableContainers) as? NSDictionary
if let parseJSON = myJSON
{
let Status = parseJSON["status"] as! Int
let Code = parseJSON["code"] as! Int
if (Status == 1 && Code == 200)
{
let Result = parseJSON["result"] as! NSArray
self.etaArray.removeAll()
//here i get the crash fatal error: Array index out of range
let latitudePickUp = self.latitudeArray[i]
let longitudePickUp = self.longitudeArray[i]
let CoordinatePickUp = CLLocation(latitude: latitudePickUp, longitude: longitudePickUp)
for res in Result
{
self.estimatedTime = ""
if (res["exists"] as! Int == 1)
{
let lastLatitude = res["latitude"] as! CLLocationDegrees
let lastLongitude = res["longitude"] as! CLLocationDegrees
let Location : CLLocation = CLLocation(latitude: lastLatitude, longitude: lastLongitude)
let meters:CLLocationDistance = Location.distanceFromLocation(CoordinatePickUp)
print(meters)
let ID = res["child_id"] as! String
let time = round(meters/40000 * 60)
self.estimatedTime = String(time)
self.etaArray.append(self.estimatedTime!)
}
else
{
self.etaArray.append("")
}
}
}
else
{
}
}
}
catch
{
print(error)
}
}
//executing the task
task.resume()
}
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.tableView.reloadData()
})
}

how to place two images on two different markers in mapKit using swift

I want to change marker image in my MKPointAnnotation() and I have successfully changed my image but problem is that I have 2 marker points and I want to place ignitionon.png on point one and ignitionof.png on point two and my logic place ignitionon.png on both the points.
Code
if(self.doubelLocation) {
var pointOff = CLLocationCoordinate2D()
var pointOn = CLLocationCoordinate2D()
if(self.dateTime.count > 0) {
for(var i : Int = 0 ; i < self.dateTime.count ; i++) {
// print("Real status = \(self.dateTime[i])")
var fixTime = self.dateTime[i]
if(fixTime == self.dateTimeTwo) {
pointOff = CLLocationCoordinate2DMake(self.latt[i], self.lngg[i]);
print("pointOff = \(pointOff)")
//points.append(pointOf)
}
if(fixTime == self.dateTimeOne){
pointOn = CLLocationCoordinate2DMake(self.latt[i], self.lngg[i]);
print("pointOn = \(pointOn)")
//points.append(pointOf)
}
}
var points: [CLLocationCoordinate2D]
points = [pointOn, pointOff]
dispatch_async(dispatch_get_main_queue(), {
let geodesic = MKGeodesicPolyline(coordinates: &points[0], count: 2)
self.theMapView.addOverlay(geodesic)
let latDelta:CLLocationDegrees = 0.03
let lnggDelta:CLLocationDegrees = 0.03
UIView.animateWithDuration(1.5, animations: { () -> Void in
let span = MKCoordinateSpanMake(latDelta, lnggDelta)
let region1 = MKCoordinateRegion(center: points[0], span: span)
self.theMapView.setRegion(region1, animated: true)
self.ActivityIndicator.stopAnimating()
for(var i : Int = 0 ;i < points.count; i++){
var st = self.reportText[i]
// let theMarker = MKPointAnnotation()
//how to change marker color
//https://stackoverflow.com/questions/33532883/add-different-pin-color-with-mapkit-in-swift-2-1
let theMarker = MKPointAnnotation()
theMarker.coordinate = points[i]
// if(st == "IGNITION ON"){
if(i == 0){
theMarker.title = "Status : IGNITION OFF"
theMarker.subtitle = "\(self.locationOff)"
// theMarker.subtitle = "Date = , Reg#: "
self.theMapView.addAnnotation(theMarker)
let anView1:MKAnnotationView = MKAnnotationView()
anView1.annotation = theMarker
anView1.image = UIImage(named:"ignitionof")
anView1.canShowCallout = true
anView1.enabled = true
}
if(i == 1){
// theMarker = UIColor.greenColor()
theMarker.title = "Status : IGNITION ON"
theMarker.subtitle = "\(self.locationOn)"
// theMarker.subtitle = "Date = , Reg#: "
self.theMapView.addAnnotation(theMarker)
//how to change image of marker
//https://stackoverflow.com/questions/24467408/swift-add-mkannotationview-to-mkmapview
let anView:MKAnnotationView = MKAnnotationView()
anView.annotation = theMarker
anView.image = UIImage(named:"ignitionon")
anView.canShowCallout = true
anView.enabled = true
}
// }
}
})
})
}
}
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
if (annotation is MKUserLocation) {
//if annotation is not an MKPointAnnotation (eg. MKUserLocation),
//return nil so map draws default view for it (eg. blue dot)...
return nil
}
let reuseId = "test"
var anView = mapView.dequeueReusableAnnotationViewWithIdentifier(reuseId)
if anView == nil {
anView = MKAnnotationView(annotation: annotation, reuseIdentifier: reuseId)
anView!.image = UIImage(named:"ignitionon")
anView!.canShowCallout = true
}
var anView1 = mapView.dequeueReusableAnnotationViewWithIdentifier(reuseId)
if anView1 == nil {
anView1 = MKAnnotationView(annotation: annotation, reuseIdentifier: reuseId)
anView1!.image = UIImage(named:"ignitionof")
anView1!.canShowCallout = true
}
else {
//we are re-using a view, update its annotation reference...
anView!.annotation = annotation
}
return anView
}
I am following this Link:
Swift - Add MKAnnotationView To MKMapView
It's not the best way to handle multiple marker with different metadata.
You can't use mapView.dequeueReusableAnnotationViewWithIdentifier(reuseId) two times or more because in viewForAnnotation it is for only 1 view for each Point you have added in the stack.
Create a sub-class of MKPointAnnotation:
class CustomPointAnnotation: MKPointAnnotation {
var tag: String!
}
Create a Dictionary with all images:
var imagesPath : ["tag_1": "image_1.png", "tag_2": "image_2.jpg"]
Now in the delegate func, check Simply
if !(annotation is CustomPointAnnotation) {
return nil
}
and Handle the only one View you have:
let reuseId = "test"
var anView = mapView.dequeueReusableAnnotationViewWithIdentifier(reuseId)
if anView == nil {
anView = MKAnnotationView(annotation: annotation, reuseIdentifier: reuseId)
var customannotation = annotation as! CustomPointAnnotation
anView!.image = UIImage(named: imagesPath(customannotation.tag))
anView!.canShowCallout = true
}
An example to add a new Custom point is:
let aPoint = CustomPointAnnotation()
aPoint.coordinate = CLLocationCoordinate2DMake(40.730872, -73.003066)
aPoint.title = "Info1"
aPoint.subtitle = "Subtitle"
aPoint.tag = "tag_1"
mapView.addAnnotation(aPoint)

Swift 2 Ambiguous use of 'subscript' error

After upgrading to new version of xcode/swift I am getting lots of 'ambiguous use of subscript' errors.
The code in question is below, with the lines causing errors labelled with comments. Any ideas?
var exch: (AnyObject) = self.form.formValues().valueForKey(Static.exchange)!
if (exch.count != nil) {
if (exch.count==1) {
ex = exch[0] as! Int // error
} else if (exch.count==2) {
var i1 = exch[0] as! Int //error
var i2 = exch[1] as! Int //error
var total = i1 + i2
ex = total
} else if (exch.count==3) {
var i1 = exch[0] as! Int //error
var i2 = exch[1] as! Int //error
var i3 = exch[2] as! Int //error
var total = i1 + i2 + i3
ex = total
} else {
ex = 99
}
}
What is a type of exch? If it is an array of AnyObject change
var exch: (AnyObject)
to
var exch: [AnyObject]

How to get MAC address from OS X with Swift

Is it possible to get the MAC address using Swift?
The MAC address being the primary address for the Wi-Fi or Airport.
I'm trying to make a OS X application.
Apple's sample code from https://developer.apple.com/library/mac/samplecode/GetPrimaryMACAddress/Introduction/Intro.html to retrieve the Ethernet MAC address can
be translated to Swift. I have preserved only the most important
comments, more explanations can be found in the original code.
// Returns an iterator containing the primary (built-in) Ethernet interface. The caller is responsible for
// releasing the iterator after the caller is done with it.
func FindEthernetInterfaces() -> io_iterator_t? {
let matchingDictUM = IOServiceMatching("IOEthernetInterface");
// Note that another option here would be:
// matchingDict = IOBSDMatching("en0");
// but en0: isn't necessarily the primary interface, especially on systems with multiple Ethernet ports.
if matchingDictUM == nil {
return nil
}
let matchingDict = matchingDictUM.takeUnretainedValue() as NSMutableDictionary
matchingDict["IOPropertyMatch"] = [ "IOPrimaryInterface" : true]
var matchingServices : io_iterator_t = 0
if IOServiceGetMatchingServices(kIOMasterPortDefault, matchingDict, &matchingServices) != KERN_SUCCESS {
return nil
}
return matchingServices
}
// Given an iterator across a set of Ethernet interfaces, return the MAC address of the last one.
// If no interfaces are found the MAC address is set to an empty string.
// In this sample the iterator should contain just the primary interface.
func GetMACAddress(intfIterator : io_iterator_t) -> [UInt8]? {
var macAddress : [UInt8]?
var intfService = IOIteratorNext(intfIterator)
while intfService != 0 {
var controllerService : io_object_t = 0
if IORegistryEntryGetParentEntry(intfService, "IOService", &controllerService) == KERN_SUCCESS {
let dataUM = IORegistryEntryCreateCFProperty(controllerService, "IOMACAddress", kCFAllocatorDefault, 0)
if dataUM != nil {
let data = dataUM.takeRetainedValue() as! NSData
macAddress = [0, 0, 0, 0, 0, 0]
data.getBytes(&macAddress!, length: macAddress!.count)
}
IOObjectRelease(controllerService)
}
IOObjectRelease(intfService)
intfService = IOIteratorNext(intfIterator)
}
return macAddress
}
if let intfIterator = FindEthernetInterfaces() {
if let macAddress = GetMACAddress(intfIterator) {
let macAddressAsString = ":".join(macAddress.map( { String(format:"%02x", $0) } ))
println(macAddressAsString)
}
IOObjectRelease(intfIterator)
}
The only "tricky" part is how to work with Unmanaged objects, those
have the suffix UM in my code.
Instead of returning an error code, the functions return an optional
value which is nil if the function failed.
Update for Swift 3:
func FindEthernetInterfaces() -> io_iterator_t? {
let matchingDict = IOServiceMatching("IOEthernetInterface") as NSMutableDictionary
matchingDict["IOPropertyMatch"] = [ "IOPrimaryInterface" : true]
var matchingServices : io_iterator_t = 0
if IOServiceGetMatchingServices(kIOMasterPortDefault, matchingDict, &matchingServices) != KERN_SUCCESS {
return nil
}
return matchingServices
}
func GetMACAddress(_ intfIterator : io_iterator_t) -> [UInt8]? {
var macAddress : [UInt8]?
var intfService = IOIteratorNext(intfIterator)
while intfService != 0 {
var controllerService : io_object_t = 0
if IORegistryEntryGetParentEntry(intfService, "IOService", &controllerService) == KERN_SUCCESS {
let dataUM = IORegistryEntryCreateCFProperty(controllerService, "IOMACAddress" as CFString, kCFAllocatorDefault, 0)
if let data = dataUM?.takeRetainedValue() as? NSData {
macAddress = [0, 0, 0, 0, 0, 0]
data.getBytes(&macAddress!, length: macAddress!.count)
}
IOObjectRelease(controllerService)
}
IOObjectRelease(intfService)
intfService = IOIteratorNext(intfIterator)
}
return macAddress
}
if let intfIterator = FindEthernetInterfaces() {
if let macAddress = GetMACAddress(intfIterator) {
let macAddressAsString = macAddress.map( { String(format:"%02x", $0) } )
.joined(separator: ":")
print(macAddressAsString)
}
IOObjectRelease(intfIterator)
}
Update for Swift 4.2
func FindEthernetInterfaces() -> io_iterator_t? {
let matchingDictUM = IOServiceMatching("IOEthernetInterface");
// Note that another option here would be:
// matchingDict = IOBSDMatching("en0");
// but en0: isn't necessarily the primary interface, especially on systems with multiple Ethernet ports.
if matchingDictUM == nil {
return nil
}
let matchingDict = matchingDictUM! as NSMutableDictionary
matchingDict["IOPropertyMatch"] = [ "IOPrimaryInterface" : true]
var matchingServices : io_iterator_t = 0
if IOServiceGetMatchingServices(kIOMasterPortDefault, matchingDict, &matchingServices) != KERN_SUCCESS {
return nil
}
return matchingServices
}
// Given an iterator across a set of Ethernet interfaces, return the MAC address of the last one.
// If no interfaces are found the MAC address is set to an empty string.
// In this sample the iterator should contain just the primary interface.
func GetMACAddress(_ intfIterator : io_iterator_t) -> [UInt8]? {
var macAddress : [UInt8]?
var intfService = IOIteratorNext(intfIterator)
while intfService != 0 {
var controllerService : io_object_t = 0
if IORegistryEntryGetParentEntry(intfService, kIOServicePlane, &controllerService) == KERN_SUCCESS {
let dataUM = IORegistryEntryCreateCFProperty(controllerService, "IOMACAddress" as CFString, kCFAllocatorDefault, 0)
if dataUM != nil {
let data = (dataUM!.takeRetainedValue() as! CFData) as Data
macAddress = [0, 0, 0, 0, 0, 0]
data.copyBytes(to: &macAddress!, count: macAddress!.count)
}
IOObjectRelease(controllerService)
}
IOObjectRelease(intfService)
intfService = IOIteratorNext(intfIterator)
}
return macAddress
}
func getMacAddress() -> String? {
var macAddressAsString : String?
if let intfIterator = FindEthernetInterfaces() {
if let macAddress = GetMACAddress(intfIterator) {
macAddressAsString = macAddress.map( { String(format:"%02x", $0) } ).joined(separator: ":")
print(macAddressAsString!)
}
IOObjectRelease(intfIterator)
}
return macAddressAsString
}
Different approach via if_msghdr
func MACAddressForBSD(bsd : String) -> String?
{
let MAC_ADDRESS_LENGTH = 6
let separator = ":"
var length : size_t = 0
var buffer : [CChar]
let bsdIndex = Int32(if_nametoindex(bsd))
if bsdIndex == 0 {
print("Error: could not find index for bsd name \(bsd)")
return nil
}
let bsdData = Data(bsd.utf8)
var managementInfoBase = [CTL_NET, AF_ROUTE, 0, AF_LINK, NET_RT_IFLIST, bsdIndex]
if sysctl(&managementInfoBase, 6, nil, &length, nil, 0) < 0 {
print("Error: could not determine length of info data structure");
return nil;
}
buffer = [CChar](unsafeUninitializedCapacity: length, initializingWith: {buffer, initializedCount in
for x in 0..<length { buffer[x] = 0 }
initializedCount = length
})
if sysctl(&managementInfoBase, 6, &buffer, &length, nil, 0) < 0 {
print("Error: could not read info data structure");
return nil;
}
let infoData = Data(bytes: buffer, count: length)
let indexAfterMsghdr = MemoryLayout<if_msghdr>.stride + 1
let rangeOfToken = infoData[indexAfterMsghdr...].range(of: bsdData)!
let lower = rangeOfToken.upperBound
let upper = lower + MAC_ADDRESS_LENGTH
let macAddressData = infoData[lower..<upper]
let addressBytes = macAddressData.map{ String(format:"%02x", $0) }
return addressBytes.joined(separator: separator)
}
MACAddressForBSD(bsd: "en0")
Update to Martin R's entry. There are a couple of lines that won't compile with Swift 2.1.
Change:
let matchingDict = matchingDictUM.takeUnretainedValue() as NSMutableDictionary
To:
let matchingDict = matchingDictUM as NSMutableDictionary
Change:
let macAddressAsString = ":".join(macAddress.map( { String(format:"%02x", $0) } ))
To:
let macAddressAsString = macAddress.map( { String(format:"%02x", $0) } ).joinWithSeparator(":")
You can also use 'SystemConfiguration' framework
import SystemConfiguration
func collectMACAddresses() -> [String] {
guard let interfaces = SCNetworkInterfaceCopyAll() as? [SCNetworkInterface] else {
return []
}
return interfaces
.map(SCNetworkInterfaceGetHardwareAddressString)
.compactMap { $0 as String? }
}
DISCLAIMER: this is not production-ready. It would probably be rejected by the App Store. It's also subject to errors if the output of ifconfig changes in the future. I've made this because I lacked the skills to translate the C code given in the links. It does not replace a full Swift solution. That being said, it works...
Get ifconfig's output and parse it to get the MAC address associated with an interface (en0 in this example):
let theTask = NSTask()
let taskOutput = NSPipe()
theTask.launchPath = "/sbin/ifconfig"
theTask.standardOutput = taskOutput
theTask.standardError = taskOutput
theTask.arguments = ["en0"]
theTask.launch()
theTask.waitUntilExit()
let taskData = taskOutput.fileHandleForReading.readDataToEndOfFile()
if let stringResult = NSString(data: taskData, encoding: NSUTF8StringEncoding) {
if stringResult != "ifconfig: interface en0 does not exist" {
let f = stringResult.rangeOfString("ether")
if f.location != NSNotFound {
let sub = stringResult.substringFromIndex(f.location + f.length)
let range = Range(start: advance(sub.startIndex, 1), end: advance(sub.startIndex, 18))
let result = sub.substringWithRange(range)
println(result)
}
}
}

Drawing performance with multiple UIBezierPaths

I have a drawing app which is currently made up of a main View Controller which holds 4 separate UIViews which simultaneously replicate the line drawn on the touched quadrant across the other 3 with some axis reversed to make the drawing symmetrical.
When using this method the drawing is smooth and you can see that there are lots of points being collected when the user moves their finger as the line follows their movements quite well.
The code at a high level looks like this:
MainViewController.swift
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
var touch: UITouch = touches.anyObject() as UITouch
var p = CGPoint()
if touch.view == quadrant1 {
p = touch.locationInView(quadrant1)
quadrant1.began(p)
var p2 = CGPointMake(quadrant2.bounds.width - p.x, p.y)
quadrant2.began(p2)
var p3 = CGPointMake(p.x,quadrant3.bounds.height - p.y)
quadrant3.began(p3)
var p4 = CGPointMake(quadrant4.bounds.width - p.x, quadrant4.bounds.height - p.y)
quadrant4.began(p4)
} else if touch.view == quadrant2 {
...
Touches 'moved' and 'ended' call similar methods in each of the quadrants by doing the same calculations. The Quadrant files look like this:
Quadrant1,2,3,4.swift
// A counter to determine if there are enough points to make a quadcurve
var ctr = 0
// The path to stroke
var path = UIBezierPath()
// After the user lifts their finger and the line has been finished the same line is rendered to an image and the UIBezierPath is cleared to prevent performance degradation when lots of lines are on screen
var incrementalImage = UIImage()
// This array stores the points that make each line
var pts: [CGPoint] = []
override func drawRect(rect: CGRect) {
incrementalImage.drawInRect(rect)
path.stroke()
}
func began (beganPoint: CGPoint) {
ctr = 0
var p = beganPoint
pts.insert(beganPoint, atIndex: 0)
}
func moved(movedPoints: CGPoint) {
var p = movedPoints
ctr++
pts.insert(movedPoints, atIndex: ctr)
// This IF statement handles the quadcurve calculations
if ctr == 3 {
pts[2] = CGPointMake((pts[1].x + pts[3].x)/2.0, (pts[1].y + pts[3].y)/2.0);
path.moveToPoint(pts[0])
path.addQuadCurveToPoint(pts[2], controlPoint: pts[1])
self.setNeedsDisplay()
pts[0] = pts[2]
pts[1] = pts[3]
ctr = 1
}
}
func ended (endPoint: CGPoint) {
if ctr == 2 {
path.moveToPoint(pts[0])
path.addQuadCurveToPoint(pts[2], controlPoint: pts[1])
}
self.drawBitmap()
self.setNeedsDisplay()
path.removeAllPoints()
}
func drawBitmap() {
UIGraphicsBeginImageContextWithOptions(self.bounds.size, false, 0.0)
var rectPath = UIBezierPath(rect: self.bounds)
UIColor.clearColor().setFill()
rectPath.fill()
incrementalImage.drawAtPoint(CGPointZero)
color.setStroke()
path.stroke()
incrementalImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
}
So the above approach actually worked very well and produce fairly smooth lines like so but the user was always locked into using 4 quadrants because they were separate UIView's:
After some thinking we decided to scrap the 4 separate UIView's and use a single view to handle the drawing which would allow an arbitrary number of lines to be drawn at a time giving the user more options (8 lines for example), and this is where things got tricky.
The MainViewController no longer handles the touches interaction methods, the new 'DrawingView' captures the gestures itself with a UILongPressGestureRecogniser.
func handleLongPressDrawing(sender: UILongPressGestureRecognizer) {
var p = sender.locationInView(self)
switch sender.state {
case UIGestureRecognizerState.Began:
self.began(p)
break;
case UIGestureRecognizerState.Changed:
self.moved(p)
break;
case UIGestureRecognizerState.Ended:
self.ended(p)
default:
break;
}
}
The methods now reference a new DrawingElement class to perform the symmetry calculations:
enum GridType {
case ONE, TWO_1, TWO_2, TWO_3, TWO_4, THREE, FOUR_1, FOUR_2, FIVE, SIX_1, SIX_2, SEVEN, EIGHT_1, SIXTEEN
}
enum DrawingElementType {
case PATH, POINT, CIRCLE
}
class DrawingElement: NSObject {
var points : [CGPoint] = []
private var drawingWidth : CGFloat!
private var drawingHeight : CGFloat!
private var gridType : GridType!
private var drawingElementType : DrawingElementType!
init(gridType : GridType, drawingWidth : CGFloat, drawingHeight : CGFloat) {
self.gridType = gridType
self.drawingWidth = drawingWidth
self.drawingHeight = drawingHeight
super.init()
}
func getPoints() -> [CGPoint] {
return points
}
func addPoint(pointCG: CGPoint) {
points.append(pointCG)
}
func getPoint(pos : Int) -> CGPoint {
return points[pos]
}
func getDrawingWidth() -> CGFloat {
return drawingWidth
}
func setDrawingWidth(w : CGFloat) {
drawingWidth = w
}
func getDrawingWidthCG() -> CGFloat {
return CGFloat(drawingWidth)
}
func getDrawingHeight() -> CGFloat {
return drawingHeight
}
func setDrawingHeight(h : CGFloat) {
drawingHeight = h
}
func getDrawingHeightCG() -> CGFloat {
return CGFloat(drawingHeight)
}
func getPointCount() -> Int {
return points.count
}
func getDrawingElementType() -> DrawingElementType {
return drawingElementType
}
func setDrawingElementType(det : DrawingElementType) {
drawingElementType = det
}
func getGridType() -> GridType {
return gridType
}
func setGridType(gt : GridType) {
gridType = gt
}
func smoothLinesPart1() {
points[2] = CGPointMake((points[1].x + points[3].x)/2.0, (points[1].y + points[3].y)/2.0)
}
func smoothLinesMoveTo() -> CGPoint {
return points[0]
}
func smoothLinesQuadCurve() -> (CGPoint, CGPoint) {
return (points[2], points[1])
}
func smoothLinesReorderArray() {
points[0] = points[2]
points[1] = points[3]
}
func getCalculatedPoints(allPoints : [CGPoint]) -> [Int : [CGPoint]] {
var newPoints = [CGPoint]()
var numberOfPoints : Int!
var temp : CGFloat!
var x : CGFloat!
var y : CGFloat!
//println("Before Path points: \(allPoints)")
var pathPoints = [Int() : [CGPoint]()]
if(gridType == GridType.EIGHT_1) {
numberOfPoints = 8
} else if(gridType == GridType.ONE) {
numberOfPoints = 1
} else if(gridType == GridType.TWO_1) {
numberOfPoints = 2
} else if(gridType == GridType.FOUR_1) {
numberOfPoints = 4
}
var firstTime = true
for point in allPoints {
x = point.x
y = point.y
if(gridType == GridType.EIGHT_1 || gridType == GridType.ONE || gridType == GridType.TWO_1 || gridType == GridType.FOUR_1) {
if(firstTime) {
for i in 1...numberOfPoints {
switch (i) {
case 5:
temp = y;
y = x;
x = temp;
pathPoints[4] = [CGPoint(x: x, y: y)]
case 1:
pathPoints[0] = [CGPoint(x: x, y: y)]
//println(" first point\(pathPoints[0])")
break;
case 2:
pathPoints[1] = [CGPoint(x: (x - getDrawingWidthCG()) * -1, y: y)]
break;
case 6:
pathPoints[5] = [CGPoint(x: (x - getDrawingWidthCG()) * -1, y: y)]
break;
case 3:
pathPoints[2] = [CGPoint(x: x, y: (y - getDrawingHeightCG()) * -1)]
break;
case 7:
pathPoints[6] = [CGPoint(x: x, y: (y - getDrawingHeightCG()) * -1)]
break;
case 4:
pathPoints[3] = [CGPoint(x: (x - getDrawingWidthCG()) * -1, y: (y - getDrawingHeightCG()) * -1)]
break;
case 8:
pathPoints[7] = [CGPoint(x: (x - getDrawingWidthCG()) * -1, y: (y - getDrawingHeightCG()) * -1)]
break;
default:
break
//newPoints.append(CGPoint(x: x, y: y))
}
}
firstTime = false
} else {
for i in 1...numberOfPoints {
switch (i) {
case 5:
temp = y;
y = x;
x = temp;
pathPoints[4]?.append(CGPoint(x: x, y: y))
case 1:
pathPoints[0]?.append(CGPoint(x: x, y: y))
//println(" first point\(pathPoints[0])")
break;
case 2:
pathPoints[1]?.append(CGPoint(x: (x - getDrawingWidthCG()) * -1, y: y))
break;
case 6:
pathPoints[5]?.append(CGPoint(x: (x - getDrawingWidthCG()) * -1, y: y))
break;
case 3:
pathPoints[2]?.append(CGPoint(x: x, y: (y - getDrawingHeightCG()) * -1))
break;
case 7:
pathPoints[6]?.append(CGPoint(x: x, y: (y - getDrawingHeightCG()) * -1))
break;
case 4:
pathPoints[3]?.append(CGPoint(x: (x - getDrawingWidthCG()) * -1, y: (y - getDrawingHeightCG()) * -1))
break;
case 8:
pathPoints[7]?.append(CGPoint(x: (x - getDrawingWidthCG()) * -1, y: (y - getDrawingHeightCG()) * -1))
break;
default:
break
//newPoints.append(CGPoint(x: x, y: y))
}
}
}
}
}
}
And this is called at various parts of the DrawingViews interaction handlers:
var paths = [Int() : UIBezierPath()]
func began (beganPoint: CGPoint) {
strokes = 0
var p = beganPoint
ctr = 0
//pts.insert(beganPoint, atIndex: 0)
drawingElement?.addPoint(beganPoint)
}
func moved(movedPoints: CGPoint) {
strokes++
var p = movedPoints
ctr++
drawingElement?.addPoint(movedPoints)
if ctr == 3 {
drawingElement?.smoothLinesPart1()
path.moveToPoint(drawingElement!.smoothLinesMoveTo())
path.addQuadCurveToPoint(drawingElement!.smoothLinesQuadCurve().0, controlPoint: drawingElement!.smoothLinesQuadCurve().1)
self.setNeedsDisplay()
drawingElement?.smoothLinesReorderArray()
ctr = 1
}
var pointsArray : [CGPoint] = drawingElement!.getPoints()
var calcArray = drawingElement?.getCalculatedPoints(pointsArray)
let sortedCalcArray = sorted(calcArray!) { $0.0 < $1.0 }
if pointsArray.count > 1 {
for (pIndex, path) in sortedCalcArray {
paths[pIndex] = UIBezierPath()
for var i = 0; i < path.count; i++ {
paths[pIndex]!.moveToPoint(path[i])
if(i > 0) {
paths[pIndex]!.addLineToPoint(path[i-1])
}
self.setNeedsDisplay()
}
}
}
override func drawRect(rect: CGRect) {
for (index, path) in paths {
path.lineCapStyle = kCGLineCapRound
path.lineWidth = lineWidth
color.setStroke()
path.stroke()
}
color.setStroke()
incrementalImage.drawInRect(rect)
}
}
I have a feeling that either 1) The iPhone does like drawing 4 or more paths within a single view at a time, or 2) the performance is degraded because of the number of loops that are running each time the user moves their finger. Here is what a similar line looks like with the above new code:
So after all of that I am wondering if anyone would be able to shed some light on why the new code draws so differently or what a better approach may be.
Thanks
So after some trial and error I kept most of the code above, the only notable difference was that I constructed 4 separate UIBezierPaths and eliminated the for loop nested in the other for loop. That seemed to be causing the issue

Resources