I'm developing an iOS application where the user is able to tap on a property boundary and the mapView is supposed to set the visible bounds in the view to display the whole property - and then highlight the property boundaries. I'm using setVisibleCoordinateBounds with a completion handler that highlights the property boundaries after the animation is done.
func updateFieldBoundariesHighlightSourceShape(featureArray : [MGLMultiPolygonFeature]?) {
if let fieldBoundariesSource = self.mapView.style?.source(withIdentifier: Coordinator.MapBoxIdentifier.fieldBoundaryHighlightSourceLayer.rawValue) as? MGLShapeSource {
let features = featureArray ?? generateFieldBoundaryFeatureArray(fields: self.fields.map({$0}))
let newShapeCollectionFeature = MGLShapeCollectionFeature(shapes: features)
fieldBoundariesSource.shape = newShapeCollectionFeature
}
}
func setHighlightedField(field: Field)
(...)
self.mapView.setVisibleCoordinateBounds(fieldFeature.overlayBounds, edgePadding: UIEdgeInsets(top: 65, left: 65, bottom: 65, right: 65), animated: true, completionHandler: {
// After zooming and positioning field on the screen, add layers to the map's style.
self.sceneStates.activeField = selectedField
self.updateFieldBoundariesHighlightSourceShape(featureArray: [fieldFeature])
})
(...)
}
This is a snippet of what I have. When the zoom is at a level that shows the whole property, it zooms in and everything is fine. The issue is, when zoomed in a small property and then selecting a larger one, zoom animation stops at some point (incomplete) and the highlight layer shows a cropped polygon compared to the property's real overlay bounds.
I have looked at this question but none of the answers fixed the issue. It looks like MapBox is getting only the polygon/boundaries from a visible/nearby tile and cropping what is outside of that tile.
I've tried using the following approaches with identical results: mapView.fly(to:), cameraThatFitsCoordinateBounds and setVisibleCoordinates(using the polygon's point's coordinates as an array of unsafe pointers)
Here is a GIF of what is going on
Related
I could not find much detail about how to add a customView as decoration for UICalenderView. There are many blogs telling how to add images but could not find anyone about CustomView. In images, we can return the decoration item with size parameter however in case of customView there is no option to pass size along with customView that you are adding. So in the end, I was able to add a view with red background, but the size is wrong. I tried to create a view and give it frame but it had no effect. So im confused how to adjust its size. Here is the method in which I add customView that im creating:
func calendarView(_ calendarView: UICalendarView, decorationFor dateComponents: DateComponents) -> UICalendarView.Decoration? {
return .customView(addActivityCircle)
}
And this is my addActivityCircle method which for now just creating a view with red background color:
private func addActivityCircle() -> UIView {
let view = UIView()
view.backgroundColor = .red
view.clipsToBounds = false
view.frame = CGRect(x: 0, y: 0, width: 50, height: 50)
return view
}
When I run this code I do see a view with red color but it's like a small rectangle, not 50x50. If I pass small values like 20x20, I do see a small rectangle but anything above that I see a rectangle of fixed size. I think that's the limit of decoration item but in apps like Fitness app by apple, there are bigger activity rings than that so there should be a way to have bigger sized custom views as this is just too small. The width is fine but the height is just too less. This is what im getting and it does not get any higher than that:
I have recently moved a bunch of UIImageViews into nested Stack Views (Horizontal and Vertical). Whenever the user presses the play button it animates, then starts a timer. Once the timer reaches 0 it is supposed to flip each card consecutively and hide the image itself. This was working until I added them to a Stack View. I've read that the stack views are buggy when it comes to this sort of thing, but any attempt at fixing it hasn't worked thus far. I'm at an impasse, can anyone point me in the right direction?
Issue Occurs In Here I Think
//INFO: Disable cards while viewing.
for card in cardsPhone
{
card.isUserInteractionEnabled = false
flipCard(sender: card)
}
DispatchQueue.global(qos: .default).asyncAfter(deadline: .now() + 5) {
for card in self.cardsPhone
{
DispatchQueue.main.async {
//THIS IS THE ANIMATION CALL THAT MAKES VIEWS DISAPPEAR.
self.flipCard(sender: card)
}
usleep(20000)
}
DispatchQueue.main.async{
//INFO: Enable cards after hidden.
for card in self.cardsPhone
{
card.isUserInteractionEnabled = true
}
//INFO: Enable play button after cards are hidden to prevent crashing layout.
self.playButtonView.isUserInteractionEnabled = true
}
}
Flip Animation Controller
func flipCard(sender: UIImageView)
{
playButtonView.isUserInteractionEnabled = false
//INFO: If there is no UIImage, assign the appropriate image from the array.
if sender.image == nil
{
sender.image = cardsImages[sender.tag]
UIView.transition(with: sender, duration: 0.3, options: .transitionFlipFromLeft, animations: nil, completion: nil)
}
//INFO: If there is an image, remove it and replace with no Image.
else
{
sender.image = nil
UIView.transition(with: sender, duration: 0.3, options: .transitionFlipFromRight, animations: nil, completion: nil)
}
}
Update
I've discovered that if I replace the image on the UIImageView then that is actually what is causing it to disappear. So the issue is related to the code above in the else statement where sender.image = UIImage(). It doesn't matter if I replace with a different image or not, the moment the image is changed it disappears.
I've read that the stack views are buggy when it comes to this sort of thing
Well, that's not so. They are in fact quite sophisticated and consistent, and their behavior is well defined.
What is a stack view? It's a builder of autolayout constraints. And that is all it is. Putting views inside a stack view means "Please configure these views with equal spacing" (or whatever your setting is for the stack view) "using autolayout constraints that you impose."
Okay, but how does the stack view do that? Autolayout requires four pieces of information, x and y position, and width and height. The stack view will supply the position, but what about the size (width and height) of its views? Well, one way to tell it is to give your views internal constraints (if they don't conflict with what else the stack view will do). But in the absence of that, it has to use the intrinsic size of its views. And the intrinsic size of an image view under autolayout is the size of the image it contains.
So everything was fine when you had image views with images that were the same size. They all had the same intrinsic size and now the stack view could space them out. But then you took away an image view's image. Now it has no image. So it has no intrinsic size. So the stack view just removes it from the row (or column or whatever it is).
Your workaround is actually quite a good one, namely, to make sure that the image view always has an image; if it is to look blank, you just give it a blank image. The image looks like the background, but it is still an image with an actual size, and so it gives the image view a size and the image view continues to hold its place.
However another solution would have been simply to give the image view a width constraint the same size as the card image's width.
Solution
I've discovered that changing an image on a UIImageView while it's inside of a stack causes it to completely disappear. This is a weird Xcode but but the solution I found was to create a custom background on the fly to get rid of the image and only have the background color. This was done with the following code snippet. It's a bit messy, but it gets the job done.
func setColorImage(viewToModify: UIImageView, colorToSet: UIColor)
{
let rect = CGRect(origin: .zero, size: viewToModify.layer.bounds.size)
UIGraphicsBeginImageContextWithOptions(rect.size, false, 0.0)
colorToSet.setFill()
UIRectFill(rect)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
guard (image?.cgImage) != nil else { return }
viewToModify.image = image
viewToModify.backgroundColor = colorToSet
viewToModify.isUserInteractionEnabled = false
}
Essentially this code creates a new cgRect, sets the color, creates a new image from that color, then applies it back to the UIImageView.
I have the following code as follows:
playView.layer.cornerRadius = 16
let gradient1 = CAGradientLayer()
gradient1.frame = playView.frame
gradient1.cornerRadius = 16
if #available(iOS 10.0, *) {
// set P3 colour
} else {
// set sRGB colour
}
gradient1.startPoint = CGPoint(x: 0, y: 0)
gradient1.endPoint = CGPoint(x: 1, y: 1)
playView.layer.insertSublayer(gradient1, at: 0)
On the 3rd line of the block of code above, I set the frame of the gradient equal to the frame I want it to fill.
When I run the app on different devices, the gradient layer will only fill the correct area if the device the app is being run on is the one selected in the Interface Builder.
I currently have the code in viewDidLoad(), and so the issue can be solved by moving the code to viewDidAppear(), but then when the app is loaded, there will be a slight delay before the gradient appears, not giving a smooth look and feel.
Is there another method I can put the code in, so that the gradient shows in the correct place, whilst at the same time being there as soon as the user sees the screen? Or alternatively, a way to make the gradient fill the view, whilst still keeping the code in viewDidLoad()?
EDIT: viewWillAppear() does not work, nor does viewWillLayoutSubviews(). Surely there must be away to solve this?
You can put inside this block:
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
}
viewDidAppear() works. This screen is the root controller - I don't know if that makes a difference or not, but there is no visible delay on applying the gradient backgrounds.
I would be interested if anyone could explain this? I have a bar chart in another part of the app and in viewDidAppear() there is code to complete the bar chart, however there is a delay in it being filled in.
Change the layer.frame property inside the viewDidLayoutSubviews
method. This is to make sure that the subview (playView) has already a proper frame.
I'm creating an app that needs to show a tableview like below image
Similar colored circles are to be matched with a line.
Which view i can add the lines?
Or need to create a new view above tableview? But still my tableview needs to be scrolled.
How can i achieve this?
Update for Bounty
I want to implement the same with incliend lines between neighbouring circles. How to achieve the same?
Demonstration below:
create design like this
Based on your requirement just hide upper line and lower line of circle
You need to create collection view in tableview cell. In collection view you create one cell. Design the same user interface like your design. Show and hide the view with matching of rule. It will not affect tableview scrolling. and with this approach you can also provide scroll in collection view cell. i can provide you coded solution if you able to provide me more information. Thanks
You can use this Third Party LIb
You need to use a combination of collection view and a table view to give support for all devices.
1.Create one collection view cell with following layout
Hide upper and lower lines as per your need
Add collection view in table view cell and managed a number of cells in collection view depending upon the current device width and item's in between spacing.
You can create a vertical label without text, set the background color with black and place it behind the circle in view hierarchy and set a width of the label as per your requirement. Then you can hide unhide the label whenever you want.
P.S.: Make sure to hide your cell separator.
I have created a demo project. You can find it here. I tried to match your requirements. You can update the collection view settings to handle the hide and show of labels.
Let me know if you have any questions.
Hope this help.
Thanks!
To connect any circle with any other circle in the cell above / below, it will be easier and cleaner to create the connection lines dynamically rather than building them into the asset as before. This part is simple. The question now is where to add them.
You could have the connection lines between every two cells be contained in the top or bottom cell of each pair, since views can show content beyond their bounds.
There's a problem with this though, regardless of which cell contains the lines. For example, if the top cell contains them, then as soon as it is scrolled up off screen, the lines will disappear when didEndDisplayingCell is called, even though the bottom cell is still completely on screen. And then scrolling slightly such that cellForRow is called, the lines will suddenly appear again.
If you want to avoid that problem, then here is one approach:
One Approach
Give your table view and cells a clear background color, and have another table view underneath to display a new cell which will contain the connection lines.
So you now have a background TVC, with a back cell, and a foreground TVC with a fore cell. You add these TVC's as children in a parent view controller (of which you can set whatever background color you like), disable user interaction on the background TVC, and peg the background TVC's content offset to the foreground TVC's content offset in an observation block, so they will stay in sync when scrolling. I've done this before; it works well. Use the same row height, and give the background TVC a top inset of half the row height.
We can make the connection lines in the back cell hug the top and bottom edges of the cell. This way circles will be connected at their centre.
Perhaps define a method in your model that calculates what connections there are, and returns them, making that a model concern.
extension Array where Element == MyModel {
/**
A connection is a (Int, Int).
(0, 0) means the 0th circle in element i is connected to the 0th circle in element j
For each pair of elements i, j, there is an array of such connections, called a mesh.
Returns n - 1 meshes.
*/
func getMeshes() -> [[(Int, Int)]] {
// Your code here
}
}
Then in your parent VC, do something like this:
class Parent_VC: UIViewController {
var observation: NSKeyValueObservation!
var b: Background_TVC!
override func viewDidLoad() {
super.viewDidLoad()
let b = Background_TVC(model.getMeshes())
let f = Foreground_TVC(model)
for each in [b, f] {
self.addChild(each)
each.view.frame = self.view.bounds
self.view.addSubview(each.view)
each.didMove(toParent: self)
}
let insets = UIEdgeInsets(top: b.tableView.rowHeight / 2, left: 0, bottom: 0, right: 0)
b.tableView.isUserInteractionEnabled = false
b.tableView.contentInset = insets
self.b = b
self.observation = f.tableView.observe(\.contentOffset, options: [.new]) { (_, change) in
let y = change.newValue!.y
self.b.tableView.contentOffset.y = y // + or - half the row height
}
}
}
Then of course there's your drawing code. You could make it a method of your back cell class (a custom cell), which will take in a mesh data structure and then draw the lines that represent it. Something like this:
class Back_Cell: UITableViewCell {
/**
Returns an image with all the connection lines drawn for the given mesh.
*/
func createMeshImage(for mesh: [(Int, Int)]) -> UIImage {
let canvasSize = self.contentView.bounds.size
// Create a new canvas
UIGraphicsBeginImageContextWithOptions(canvasSize, false, 0)
// Grab that canvas
let canvas = UIGraphicsGetCurrentContext()!
let spacing: CGFloat = 10.0 // whatever the spacing between your circles is
// Draw the lines
for each in mesh {
canvas.move(to: CGPoint(x: CGFloat(each.0) * spacing, y: 0))
canvas.addLine(to: CGPoint(x: CGFloat(each.1) * spacing, y: self.contentView.bounds.height))
}
canvas.setStrokeColor(UIColor.black.cgColor)
canvas.setLineWidth(3)
canvas.strokePath()
let image = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return image
}
}
You'd probably want to create a Mesh class and store the images in that model, to avoid redrawing.
I have a scroll view that zooms only horizontally on pinch (based on this answer)
The content view has several children that are evenly spaced horizontally, and placed at different heights. Basically, I'm plotting a graph with dots.
Each marker is a custom UIView subclass that has a label as subview and draws a red circle inside drawRect(_:).
(The scroll view and its only child the content view use Autolayout, but all subviews of the content view are placed with frames calculated at runtime; no constraints except the label positioning respect to the marker)
I have modified the answer linked above, so that -when zooming- the dots get more spaced horizontally, but stay the same size.
This is part of the code for my content view:
override var transform: CGAffineTransform {
get {
return super.transform
}
set {
if let unzoomedViewHeight = unzoomedViewHeight {
// ^Initial height of the content view, captured
// on layoutSubviews() and used to calculate a
// a zoom transform that preserves the height
// (see linked answer for details)
// 1. Ignore vertical zooming for this view:
var modified = newValue
modified.d = 1.0
modified.ty = (1.0 - modified.a) * unzoomedViewHeight/2
super.transform = modified
// 2. Ignore zooming altogether in marker subviews:
var markerTransform = CGAffineTransformInvert(newValue)
markerTransform.d = 1.0 // No y-scale
for subview in subviews where subview is MarkerView {
subview.transform = markerTransform
}
}
}
}
Next, I want to connect my dots with straight line segments. The problem is, when I zoom the scroll view the segments become blurry.
In addition to the blurring, and because I am only zooming horizontally, the segments "shear" a bit (depending on their their slope), and thus the line width becomes uneven:
(Right now I am placing an intermediate view between each pair of dots, that draws a single segment - but the result is the same with a single path)
I have tried making the segment-drawing views aware of the transform that is applied to them by their parent, revert it, and instead modify their frame accordingly (and redraw the path within the new bounds); however it doesn't seem to work.
What is the best way to draw zoom-resistant bezier paths inside a scroll view?
Update:
I got rid of the pixelation by following this answer to a similar question (almost duplicate?); in my case the code translates to:
func scrollViewDidEndZooming(scrollView: UIScrollView, withView view: UIView?, atScale scale: CGFloat)
{
for segment in contentView.subviews where segment is SegmentView {
segment.layer.contentsScale = scale
segment.setNeedsDisplay()
}
}
...However, I still have the uneven line width issue due to stretching the (diagonal) segments only in the horizontal direction.
My initial thought, having not done this before exactly, is to store the graph as a bezier path rather than as a set of view line and point views, and based on a single unit axis. You could convert your data into this form just for drawing.
So, all points on the bezier path are normalised into the range 0 to 1.
Once you've done that you can apply a transform to the bezier path to translate (move) and scale (zoom) to the part you want and then draw the bezier path at full resolution.
This will work and is different to your current situation because your current code draws the views and then scales then so you get artifacts. The above option scales the path and then draws it.