Tracking multiple objects in iOS 13 - ios13

my app uses the example code by Apple to track multiple objects of a video using Vision (in my case, it tracks the path of a barbell during weightlifting exercises), but after updating to iOS 13 the video is not displayed properly. Instead of filling the screen like it used to do, now the video is cropped and you can only see a small portion of it. I've talked to Apple Technical support and they acknowledge the bug, but a fix is not in their plans.
What bugs me the most is that a) landscape videos are working, but not portrait videos and b) the bug only happens in real devices and not in the simulator. See attached the portion of the code used to display the video depending on its proportions (landscape or portrait).
private func scaleImage(to viewSize: CGSize) -> UIImage? {
guard self.image != nil && self.image.size != CGSize.zero else {
return nil
}
self.imageAreaRect = CGRect.zero
// There are two possible cases to fully fit self.image into the the ImageTrackingView area:
// Option 1) image.width = view.width ==> image.height <= view.height
// Option 2) image.height = view.height ==> image.width <= view.width
let imageAspectRatio = self.image.size.width / self.image.size.height
// Check if we're in Option 1) case and initialize self.imageAreaRect accordingly
let imageSizeOption1 = CGSize(width: viewSize.width, height: floor(viewSize.width / imageAspectRatio))
if imageSizeOption1.height <= viewSize.height {
print("Landscape. View size: \(viewSize)")
let imageX: CGFloat = 0
let imageY = floor((viewSize.height - imageSizeOption1.height) / 2.0)
self.imageAreaRect = CGRect(x: imageX,
y: imageY,
width: imageSizeOption1.width,
height: imageSizeOption1.height)
}
if self.imageAreaRect == CGRect.zero {
// Check if we're in Option 2) case if Option 1) didn't work out and initialize imageAreaRect accordingly
print("portrait. View size: \(viewSize)")
let imageSizeOption2 = CGSize(width: floor(viewSize.height * imageAspectRatio), height: viewSize.height)
if imageSizeOption2.width <= viewSize.width {
let imageX = floor((viewSize.width - imageSizeOption2.width) / 2.0)
let imageY: CGFloat = 0
self.imageAreaRect = CGRect(x: imageX,
y: imageY,
width: imageSizeOption2.width,
height: imageSizeOption2.height)
}
}
// In next line, pass 0.0 to use the current device's pixel scaling factor (and thus account for Retina resolution).
// Pass 1.0 to force exact pixel size.
UIGraphicsBeginImageContextWithOptions(self.imageAreaRect.size, false, 0.0)
self.image.draw(in: CGRect(x: 0.0, y: 0.0, width: self.imageAreaRect.size.width, height: self.imageAreaRect.size.height))
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage
}
Any help would be very appreciated.
Thanks

after several weeks stalking Apple Technical Support, they came out with a very simple workaround. Basically, you just need to do a conversion via CGImage instead of going directly from CIImage to UIImage.
This is the old code (as you can see in current example in Apple's documentation)
if let frame = frame {
let ciImage = CIImage(cvPixelBuffer: frame).transformed(by: transform)
let uiImage = UIImage(ciImage: ciImage)
self.trackingView.image = uiImage
}
And this is the correction
if let frame = frame {
let ciImage = CIImage(cvPixelBuffer: frame).transformed(by: transform)
guard let cgImage = CIContext.init().createCGImage(ciImage, from: ciImage.extent) else {
return
}
let uiImage = UIImage(cgImage: cgImage)
self.trackingView.image = uiImage
}
Hope this helps!

Related

ImageCapture cameraX orientation detect when is locked

I want to capture an Image and a Video using the CameraX library. By following the documentation i have no problem implementing the preview and the capture use cases. The code i use is the following.
private fun startCamera() {
cameraProviderFuture = ProcessCameraProvider.getInstance(this)
cameraProviderFuture.addListener({ setUpCamera() }, cameraExecutor)
}
private fun setUpCamera() {
cameraProvider = cameraProviderFuture.get()
bindPreview()
}
private fun bindPreview() {
preview = Preview.Builder().build().also {
it.setSurfaceProvider(binding.cameraPreview.surfaceProvider)
}
imageCapture = ImageCapture.Builder()
.setFlashMode(flashMode)
.setCaptureMode(CAPTURE_MODE_MAXIMIZE_QUALITY)
.build()
videoCapture = ...
bindCamera()
}
private fun bindCamera() {
cameraSelector = selectExternalOrBestCamera()
cameraProvider.unbindAll()
camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageCapture, videoCapture)
Now, let's say that i have locked my device orientation from the menu panel. So if i rotate de device, the applications do not rotate at all. In that case, if a capture an Image, the captured image which i save and i want to send to a Server is rotated by 90 degrees,, which is reasonable since i rotated the device and capture a photo.
As i can see, in other applications (like Whatsapp) the same senario does not happen since they show the preview image correctly rotated after the capture. How can i solve that issue?
So finally i found the solution.
val rotation = display?.rotation ?: Surface.ROTATION_0
val isLandscape = rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270
imageCapture = ImageCapture.Builder()
.setFlashMode(flashMode)
.setCaptureMode(CAPTURE_MODE_MAXIMIZE_QUALITY)
.setTargetRotation(rotation)
.setTargetResolution(
if (isLandscape)
Size(1920, 1080)
else
Size(1080, 1920)
)
.build()
That gives me the preview image that i capture in the desired orientation

How to set image in google marker with a border in Android?

I have profile photo of users stored in Firebase and I want to know how I can create a marker with the user's profile photo with an orange border.
I tried some code from the internet and it works but the measurements seem to be wrong and I don't know what I'm doing wrong.
The code I used:
fun setMarkerPhoto(user:User, location: Location){
var bitmapFinal : Bitmap?
if(hasProfilePhoto){
/*val options = RequestOptions()
options.centerCrop()*/
Glide.with(this)
.asBitmap()
/*.apply(options)*/
.centerCrop()
.load(user.image)
.into(object : CustomTarget<Bitmap>(){
override fun onResourceReady(resource: Bitmap, transition: com.bumptech.glide.request.transition.Transition<in Bitmap>?) {
bitmapFinal = createUserBitmapFinal(resource)
markerOptions
.position(LatLng(location!!.latitude, location!!.longitude))
.title("Current Location")
.snippet(address)
.icon(BitmapDescriptorFactory.fromBitmap(bitmapFinal))
mCurrentMarker = googleMap.addMarker(markerOptions)
}
override fun onLoadCleared(placeholder: Drawable?) {
TODO("Not yet implemented")
}
})
}else{
markerOptions
.position(LatLng(mLastLocation!!.latitude, mLastLocation!!.longitude))
.title("Current Location")
.snippet(address)
.icon(BitmapDescriptorFactory.fromBitmap(smallMarker))
mCurrentMarker = googleMap.addMarker(markerOptions)
}
}
private fun createUserBitmapFinal(bitmapInicial: Bitmap?): Bitmap? {
var result: Bitmap? = null
try {
result = Bitmap.createBitmap(150,150, Bitmap.Config.ARGB_8888) //change the size of the placeholder
result.eraseColor(Color.TRANSPARENT)
val canvas = Canvas(result)
val drawable: Drawable = resources.getDrawable(R.drawable.ic_pickup)
drawable.setBounds(0, 0, 150,150) //change the size of the placeholder, but you need to maintain the same proportion of the first line
drawable.draw(canvas)
val roundPaint = Paint(Paint.ANTI_ALIAS_FLAG)
val bitmapRect = RectF()
canvas.save()
if (bitmapInicial != null) {
val shader =
BitmapShader(bitmapInicial, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
val matrix = Matrix()
val scale: Float = 200 / bitmapInicial.width.toFloat() //reduce or augment here change the size of the original bitmap inside the placehoder.
// But you need to adjust the line bitmapRect with the same proportion
matrix.postTranslate(5f, 5f)
matrix.postScale(scale, scale)
roundPaint.shader = shader
shader.setLocalMatrix(matrix)
bitmapRect[10f, 10f, 104f+10f]=104f+10f //change here too to change the size
canvas.drawRoundRect(bitmapRect, 56f, 56f, roundPaint)
}
I didn't really understand how to perfectly fit the bitmap image inside the placeholder. My marker looked like this:
also the image wasn't being center cropped even though I mentioned that it should be in the code, where it says Glide.centerCrop()
Also, I'm using GeoFire to display markers of users in a specified radius of the user and for now I can display a simple marker but I want the marker to have that user's profile photo too! How can I do it?
GeoFire Code:
val geoQuery: GeoQuery = geoFire.queryAtLocation(GeoLocation(location.latitude, location.longitude), 0.5)
geoQuery.addGeoQueryEventListener(object : GeoQueryEventListener {
override fun onKeyEntered(key: String, location: GeoLocation) {
println(String.format("Key %s entered the search area at [%f,%f]", key, location.latitude, location.longitude))
Log.i("key entered","User found around you")
val aroundYou = LatLng(location.latitude, location.longitude)
if (markerList != null) {
for (marker in markerList) {
marker.remove()
}
}
otherMarkerOptions
.position(aroundYou)
.title("Current Location")
//.snippet(address)
.icon(BitmapDescriptorFactory.fromBitmap(smallMarker)) //This is a simple marker but i want it to have the user's profile photo
markerList.add(googleMap.addMarker(otherMarkerOptions))
//}
}
Thank you in advance
Edit:
In the line: val drawable: Drawable = resources.getDrawable(R.drawable.ic_pickup)
It's this png:
I want to insert the profile photo of the user on that drawable file and if the user doesn't have a profile photo then only the drawable photo will be visible.
You get the code to transform the bitmap from my code in another question in StrackOverflow. As I mentioned there, I can´t teste the code because i´m only working with flutter right now.
But looking ate your code I might try this:
Add this function:
fun dp(value: Float): Int {
return if (value == 0f) {
0
} else Math.ceil(resources.displayMetrics.density * value.toDouble()).toInt()
}
in your lines:
result = Bitmap.createBitmap(150,150, Bitmap.Config.ARGB_8888);
drawable.setBounds(0, 0, 150,150);
change to:
result = Bitmap.createBitmap(dp(62f), dp(76f), Bitmap.Config.ARGB_8888);
drawable.setBounds(0, 0, dp(150f), dp(150f)) ;
let me know the results.

Matter.js Gravity Point

Is it possible to create a single gravity / force point in matter.js that is at the center of x/y coordinates?
I have managed to do it with d3.js but wanted to enquire about matter.js as it has the ability to use multiple polyshapes.
http://bl.ocks.org/mbostock/1021841
The illustrious answer has arisen:
not sure if there is any interest in this. I'm a fan of what you have created. In my latest project, I used matter-js but I needed elements to gravitate to a specific point, rather than into a general direction. That was very easily accomplished. I was wondering if you are interested in that feature as well, it would not break anything.
All one has to do is setting engine.world.gravity.isPoint = true and then the gravity vector is used as point, rather than a direction. One might set:
engine.world.gravity.x = 355;
engine.world.gravity.y = 125;
engine.world.gravity.isPoint = true;
and all objects will gravitate to that point.
If this is not within the scope of this engine, I understand. Either way, thanks for the great work.
You can do this with the matter-attractors plugin. Here's their basic example:
Matter.use(
'matter-attractors' // PLUGIN_NAME
);
var Engine = Matter.Engine,
Events = Matter.Events,
Runner = Matter.Runner,
Render = Matter.Render,
World = Matter.World,
Body = Matter.Body,
Mouse = Matter.Mouse,
Common = Matter.Common,
Bodies = Matter.Bodies;
// create engine
var engine = Engine.create();
// create renderer
var render = Render.create({
element: document.body,
engine: engine,
options: {
width: Math.min(document.documentElement.clientWidth, 1024),
height: Math.min(document.documentElement.clientHeight, 1024),
wireframes: false
}
});
// create runner
var runner = Runner.create();
Runner.run(runner, engine);
Render.run(render);
// create demo scene
var world = engine.world;
world.gravity.scale = 0;
// create a body with an attractor
var attractiveBody = Bodies.circle(
render.options.width / 2,
render.options.height / 2,
50,
{
isStatic: true,
// example of an attractor function that
// returns a force vector that applies to bodyB
plugin: {
attractors: [
function(bodyA, bodyB) {
return {
x: (bodyA.position.x - bodyB.position.x) * 1e-6,
y: (bodyA.position.y - bodyB.position.y) * 1e-6,
};
}
]
}
});
World.add(world, attractiveBody);
// add some bodies that to be attracted
for (var i = 0; i < 150; i += 1) {
var body = Bodies.polygon(
Common.random(0, render.options.width),
Common.random(0, render.options.height),
Common.random(1, 5),
Common.random() > 0.9 ? Common.random(15, 25) : Common.random(5, 10)
);
World.add(world, body);
}
// add mouse control
var mouse = Mouse.create(render.canvas);
Events.on(engine, 'afterUpdate', function() {
if (!mouse.position.x) {
return;
}
// smoothly move the attractor body towards the mouse
Body.translate(attractiveBody, {
x: (mouse.position.x - attractiveBody.position.x) * 0.25,
y: (mouse.position.y - attractiveBody.position.y) * 0.25
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.12.0/matter.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/matter-attractors#0.1.6/build/matter-attractors.min.js"></script>
Historical note: the "gravity point" functionality was proposed as a feature in MJS as PR #132 but it was closed, with the author of MJS (liabru) offering the matter-attractors plugin as an alternate. At the time of writing, this answer misleadingly seems to indicate that functionality from the PR was in fact merged.
Unfortunately, the attractors library is 6 years outdated at the time of writing and raises a warning when using a newer version of MJS than 0.12.0. From discussion in issue #11, it sounds like it's OK to ignore the warning and use this plugin with, for example, 0.18.0. Here's the warning:
matter-js: Plugin.use: matter-attractors#0.1.4 is for matter-js#^0.12.0 but installed on matter-js#0.18.0.
Behavior seemed fine on cursory glance, but I'll keep 0.12.0 in the above example to silence it anyway. If you do update to a recent version, note that Matter.World is deprecated and should be replaced with Matter.Composite and engine.gravity.

Core Text - NSAttributedString line height done right?

I'm completely in the dark with Core Text's line spacing. I'm using NSAttributedString and I specify the following attributes on it:
- kCTFontAttributeName
- kCTParagraphStyleAttributeName
From this the CTFrameSetter gets created and drawn to context.
In the paragraph style attribute I'd like to specify the height of the lines.
When I use kCTParagraphStyleSpecifierLineHeightMultiple each line receives padding at the top of the text, instead of the text being displayed in the middle of this height.
When I use kCTParagraphStyleSpecifierLineSpacing a padding is added to the bottom of the text.
Please help me achieve a specified line height with the text(glyphs) in the middle of that height, instead of the text sitting either at the bottom or the top of the line.
Is this not possible without going down the route of explicitly creating CTLine 's and so forth?
Objective-C
NSInteger strLength = [myString length];
NSMutableParagraphStyle *style = [[NSMutableParagraphStyle alloc] init];
[style setLineSpacing:24];
[attString addAttribute:NSParagraphStyleAttributeName
value:style
range:NSMakeRange(0, strLength)];
Swift 5
let strLength = myString.length()
var style = NSMutableParagraphStyle()
style.lineSpacing = 24
attString.addAttribute(.paragraphStyle, value: style, range: NSRange(location: 0, length: strLength))
I'm still not 100% confident in my following statements, but it seems to make sense. Please correct me where I am wrong.
The line height (leading) refers to the distance between the baselines of successive lines of type. The baseline here can be interpreted as the imaginary line which the text sits on.
Spacing is the space between lines. The space appears after the line of text.
I ended up using the following solution to my problem:
// NOT SURE WHAT THE THEORY BEHIND THIS FACTOR IS. WAS FOUND VIA TRIAL AND ERROR.
CGFloat factor = 14.5/30.5;
CGFloat floatValues[4];
floatValues[0] = self.lineHeight * factor/(factor + 1);
floatValues[1] = self.lineHeight/(factor + 1);
floatValues[2] = self.lineHeight;
This matrix is used with the paragraph style parameter for NSAttributedString:
CTParagraphStyleSetting paragraphStyle[3];
paragraphStyle[0].spec = kCTParagraphStyleSpecifierLineSpacing;
paragraphStyle[0].valueSize = sizeof(CGFloat);
paragraphStyle[0].value = &floatValues[0];
paragraphStyle[1].spec = kCTParagraphStyleSpecifierMinimumLineHeight;
paragraphStyle[1].valueSize = sizeof(CGFloat);
paragraphStyle[1].value = &floatValues[1];
paragraphStyle[2].spec = kCTParagraphStyleSpecifierMaximumLineHeight;
paragraphStyle[2].valueSize = sizeof(CGFloat);
paragraphStyle[2].value = &floatValues[2];
CTParagraphStyleRef style = CTParagraphStyleCreate((const CTParagraphStyleSetting*) &paragraphStyle, 3);
[attributedString addAttribute:(NSString*)kCTParagraphStyleAttributeName value:(id)style range:NSMakeRange(0, [string length])];
CFRelease(style);
Hope this helps someone. I'll update this answer as I discover more relevant information.
In Swift 3:
let textFont = UIFont(name: "Helvetica Bold", size: 20)!
let textColor = UIColor(white: 1, alpha: 1) // White
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.paragraphSpacing = 20 // Paragraph Spacing
paragraphStyle.lineSpacing = 40 // Line Spacing
let textFontAttributes = [
NSFontAttributeName: textFont,
NSForegroundColorAttributeName: textColor,
NSParagraphStyleAttributeName: paragraphStyle
] as [String : Any]
You can set/update line spacing and line height multiple from storyboard as well as programatically.
From Interface Builder:
Programmatically:
SWift 4
extension UILabel {
// Pass value for any one of both parameters and see result
func setLineSpacing(lineSpacing: CGFloat = 0.0, lineHeightMultiple: CGFloat = 0.0) {
guard let labelText = self.text else { return }
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineSpacing = lineSpacing
paragraphStyle.lineHeightMultiple = lineHeightMultiple
let attributedString:NSMutableAttributedString
if let labelattributedText = self.attributedText {
attributedString = NSMutableAttributedString(attributedString: labelattributedText)
} else {
attributedString = NSMutableAttributedString(string: labelText)
}
// Line spacing attribute
// Swift 4.2++
attributedString.addAttribute(NSAttributedString.Key.paragraphStyle, value:paragraphStyle, range:NSMakeRange(0, attributedString.length))
// Swift 4.1--
attributedString.addAttribute(NSAttributedStringKey.paragraphStyle, value:paragraphStyle, range:NSMakeRange(0, attributedString.length))
self.attributedText = attributedString
}
}
Now call extension function
let label = UILabel()
let stringValue = "How to\ncontrol\nthe\nline spacing\nin UILabel"
// Pass value for any one argument - lineSpacing or lineHeightMultiple
label.setLineSpacing(lineSpacing: 2.0) . // try values 1.0 to 5.0
// or try lineHeightMultiple
//label.setLineSpacing(lineHeightMultiple = 2.0) // try values 0.5 to 2.0
Or using label instance (Just copy & execute this code to see result)
let label = UILabel()
let stringValue = "How to\ncontrol\nthe\nline spacing\nin UILabel"
let attrString = NSMutableAttributedString(string: stringValue)
var style = NSMutableParagraphStyle()
style.lineSpacing = 24 // change line spacing between paragraph like 36 or 48
style.minimumLineHeight = 20 // change line spacing between each line like 30 or 40
// Swift 4.2++
// Line spacing attribute
attrString.addAttribute(NSAttributedString.Key.paragraphStyle, value: style, range: NSRange(location: 0, length: stringValue.characters.count))
// Character spacing attribute
attrString.addAttribute(NSAttributedString.Key.kern, value: 2, range: NSMakeRange(0, attrString.length))
// Swift 4.1--
// Line spacing attribute
attrString.addAttribute(NSAttributedStringKey.paragraphStyle, value: style, range: NSRange(location: 0, length: stringValue.characters.count))
// Character spacing attribute
attrString.addAttribute(NSAttributedStringKey.kern, value: 2, range: NSMakeRange(0, attrString.length))
label.attributedText = attrString
Swift 3
let label = UILabel()
let stringValue = "How to\ncontrol\nthe\nline spacing\nin UILabel"
let attrString = NSMutableAttributedString(string: stringValue)
var style = NSMutableParagraphStyle()
style.lineSpacing = 24 // change line spacing between paragraph like 36 or 48
style.minimumLineHeight = 20 // change line spacing between each line like 30 or 40
attrString.addAttribute(NSParagraphStyleAttributeName, value: style, range: NSRange(location: 0, length: stringValue.characters.count))
label.attributedText = attrString
I tried all these answers, but to really get the EXACT line height that usually comes in design files from Sketch or Zeplin then you need to:
let ps = NSMutableParagraphStyle()
ps.minimumLineHeight = 34
ps.maximumLineHeight = 34
let attrText = NSAttributedString(
string: "Your long multiline text that will have exact line height spacing",
attributes: [
.paragraphStyle: ps
]
)
someLabel.attributedText = attrText
someLabel.numberOfLines = 2
...
I made an extension for this, see below. With the extension you can just set the line height like so:
let label = UILabel()
label.lineHeight = 19
This is the extension:
// Put this in a file called UILabel+Lineheight.swift, or whatever else you want to call it
import UIKit
extension UILabel {
var lineHeight: CGFloat {
set {
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.minimumLineHeight = newValue
paragraphStyle.maximumLineHeight = newValue
_setAttribute(key: NSAttributedString.Key.paragraphStyle, value: paragraphStyle)
}
get {
let paragraphStyle = _getAttribute(key: NSAttributedString.Key.paragraphStyle) as? NSParagraphStyle
return paragraphStyle?.minimumLineHeight ?? 0
}
}
func _getAttribute(key: NSAttributedString.Key) -> Any? {
return attributedText?.attribute(key, at: 0, effectiveRange: .none)
}
func _setAttribute(key: NSAttributedString.Key, value: Any) {
let attributedString: NSMutableAttributedString!
if let currentAttrString = attributedText {
attributedString = NSMutableAttributedString(attributedString: currentAttrString)
} else {
attributedString = NSMutableAttributedString(string: text ?? "")
text = nil
}
attributedString.addAttribute(key,
value: value,
range: NSRange(location: 0, length: attributedString.length))
attributedText = attributedString
}
}
Notes:
I don't like line height multiples. My design document contains a height, like 20, not a multiple.
lineSpacing as in some other answers is something totally different. Not what you want.
The reason there's an extra _set/_getAttribute method in there is that I use the same method for setting letter spacing. Could also be used for any other NSAttributedString values but seems like I'm good with just letter spacing (kerning in Swift/UIKit) and line height.
There are two properties of NSParagraphStyle that modify the height between successive text baselines in the same paragraph: lineSpacing and lineHeightMultiple. #Schoob is right that a lineHeightMultiple above 1.0 adds additional space above the text, while a lineSpacing above 0.0 adds space below the text. This diagram shows how the various dimensions are related.
To get the text to stay centred the aim is therefore to specify one in terms of the other, in such a way that any 'padding' we add by one attribute (top/bottom) is balanced by determining the other attribute's padding (bottom/top) to match. In other words, any extra space added is distributed evenly while otherwise preserving the text's existing positioning.
The nice thing is that this way you can choose which attribute you want to specify and then just determine the other:
extension UIFont
{
func lineSpacingToMatch(lineHeightMultiple: CGFloat) -> CGFloat {
return self.lineHeight * (lineHeightMultiple - 1)
}
func lineHeightMultipleToMatch(lineSpacing: CGFloat) -> CGFloat {
return 1 + lineSpacing / self.lineHeight
}
}
From here, other answers show how these two attributes can be set in an NSAttributedString, but this should answer how the two can be related to 'centre' the text.
Swift 4 & 5
extension NSAttributedString {
/// Returns a new instance of NSAttributedString with same contents and attributes with line spacing added.
/// - Parameter spacing: value for spacing you want to assign to the text.
/// - Returns: a new instance of NSAttributedString with given line spacing.
func withLineSpacing(_ spacing: CGFloat) -> NSAttributedString {
let attributedString = NSMutableAttributedString(attributedString: self)
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineBreakMode = .byTruncatingTail
paragraphStyle.lineSpacing = spacing
attributedString.addAttribute(.paragraphStyle,
value: paragraphStyle,
range: NSRange(location: 0, length: string.count))
return NSAttributedString(attributedString: attributedString)
}
}
This worked for me in Xcode 7.2. iOS 9.2.1. (Swift 2.1.):
dispatch_async(dispatch_get_main_queue()) { () -> Void in
let paragraphStyleWithSpacing = NSMutableParagraphStyle()
paragraphStyleWithSpacing.lineSpacing = 2.0 //CGFloat
let textWithLineSpacing = NSAttributedString(string: str, attributes: [NSParagraphStyleAttributeName : paragraphStyleWithSpacing])
self.MY_TEXT_VIEW_NAME.attributedText = textWithLineSpacing
}
Another way of twerking with a NSAttributedString line position is playing with
baselineOffset attribute:
let contentText = NSMutableAttributedString(
string: "I see\nI'd think it`d be both a notification and a\nplace to see past announcements\nLike a one way chat.")
contentText.addAttribute(.baselineOffset, value: 10, range: NSRange(location: 0, length: 5))
contentText.addAttribute(.baselineOffset, value: -10, range: NSRange(location: 85, length: 20))
Result:
"I see
I'd think it`d be both a notification and a
place to see past announcements
Like a one way chat."
https://stackoverflow.com/a/55876401/4683601

Find which tiles are currently visible in the viewport of a Google Maps v3 map

I am trying to build support for tiled vector data into some of our Google Maps v3 web maps, and I'm having a hard time figuring out how to find out which 256 x 256 tiles are visible in the current map viewport. I know that the information needed to figure this out is available if you create a google.maps.ImageMapType like here: Replacing GTileLayer in Google Maps v3, with ImageMapType, Tile bounding box?, but I'm obviously not doing this to bring in traditional pre-rendered map tiles.
So, a two part question:
What is the best way to find out which tiles are visible in the current viewport?
Once I have this information, what is the best way to go about converting it into lat/lng bounding boxes that can be used to request the necessary data? I know I could store this information on the server, but if there is an easy way to convert on the client it would be nice.
Here's what I came up with, with help from the documentation (http://code.google.com/apis/maps/documentation/javascript/maptypes.html, especially the "Map Coordinates" section) and a number of different sources:
function loadData() {
var bounds = map.getBounds(),
boundingBoxes = [],
boundsNeLatLng = bounds.getNorthEast(),
boundsSwLatLng = bounds.getSouthWest(),
boundsNwLatLng = new google.maps.LatLng(boundsNeLatLng.lat(), boundsSwLatLng.lng()),
boundsSeLatLng = new google.maps.LatLng(boundsSwLatLng.lat(), boundsNeLatLng.lng()),
zoom = map.getZoom(),
tiles = [],
tileCoordinateNw = pointToTile(boundsNwLatLng, zoom),
tileCoordinateSe = pointToTile(boundsSeLatLng, zoom),
tileColumns = tileCoordinateSe.x - tileCoordinateNw.x + 1;
tileRows = tileCoordinateSe.y - tileCoordinateNw.y + 1;
zfactor = Math.pow(2, zoom),
minX = tileCoordinateNw.x,
minY = tileCoordinateNw.y;
while (tileRows--) {
while (tileColumns--) {
tiles.push({
x: minX + tileColumns,
y: minY
});
}
minY++;
tileColumns = tileCoordinateSe.x - tileCoordinateNw.x + 1;
}
$.each(tiles, function(i, v) {
boundingBoxes.push({
ne: projection.fromPointToLatLng(new google.maps.Point(v.x * 256 / zfactor, v.y * 256 / zfactor)),
sw: projection.fromPointToLatLng(new google.maps.Point((v.x + 1) * 256 / zfactor, (v.y + 1) * 256 / zfactor))
});
});
$.each(boundingBoxes, function(i, v) {
var poly = new google.maps.Polygon({
map: map,
paths: [
v.ne,
new google.maps.LatLng(v.sw.lat(), v.ne.lng()),
v.sw,
new google.maps.LatLng(v.ne.lat(), v.sw.lng())
]
});
polygons.push(poly);
});
}
function pointToTile(latLng, z) {
var projection = new MercatorProjection();
var worldCoordinate = projection.fromLatLngToPoint(latLng);
var pixelCoordinate = new google.maps.Point(worldCoordinate.x * Math.pow(2, z), worldCoordinate.y * Math.pow(2, z));
var tileCoordinate = new google.maps.Point(Math.floor(pixelCoordinate.x / MERCATOR_RANGE), Math.floor(pixelCoordinate.y / MERCATOR_RANGE));
return tileCoordinate;
};
An explanation: Basically, everytime the map is panned or zoomed, I call the loadData function. This function calculates which tiles are in the map view, then iterates through the tiles that are already loaded and deletes the ones that are no longer in the view (I took this portion of code out, so you won't see it above). I then use the LatLngBounds stored in the boundingBoxes array to request data from the server.
Hope this helps someone else...
For more recent users, it's possible to get tile images from the sample code in the documentation on this page of the Google Maps Javascript API documentation.
Showing Pixel and Tile Coordinates

Resources