How to trace which image is currently seen in my Uiscrollview? - ios

I am new to iOS.
I am working on UIScrollView based application. I have 3 images in UIScrollView.
I have 2 UIButtons which have selector method for Facebook and Twitter sharing.
I want to share image which is currently seen in UIScrollView to Facebook & Twitter.
I am stuck at extracting image at current scroll point.
After Googling I think something like UIPageController is the solution for me.
But I do not have any idea about how to implement it.
Please help me sort it out. Thank You!

I would say you have multiple options here, this is really a design question.
An easy (but also not very elegant) approach would be to fetch the current offset of the scroll view using its contentOffset property (depending on whether your scroll view scrolls horizontically or vertically, you have ease to use the x or y value of the contentOffset).
Next, you get the position values from your image views (again either myImageView.frame.origin.x or myImageView.frame.origin.y) and you can then calculate which of the image views is currently in the offset of the scroll view.

You can use a page controller (basically, you allow the user to end its scrolling on specific index).
Or when the user touches a button, you can retrieve the contentOffset of the scrollView and compare this value with the origin of the frame your images.
Assume, you created your images with these frames:
image1.frame = CGRectMake(0,0,320,250);
image2.frame = CGRectMake(0,250,320,250);
image3.frame = CGRectMake(0,500,320,250);
Now when you get the action:
-(IBAction)share:(id)sender {
CGPoint offset = self.scrollView.contentOffset;
if (offset.y < 250) {
//First image selected
}else if (offset.y > 500) {
//Third image selected
} else {
//Second image selected
}
}

Related

Best way to add multiple diagonal connection lines between TableViewCells

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.

How to scroll UICollectionView that is underneath another UICollectionView?

So heres my issue, the 4 orange rectangles you see on the gif are a single vertical UICollectionView = orangeCollectionView.
The Green and Purple "card" views are part of another UICollectionView = overlayCollectionView.
overlayCollectionView has 3 cells, one of which is just a blank UICollectionViewCell, the other 2 are the cards.
When the overlayCollectionView is showing the blank UICollectionViewCell, I want to be able to scroll the orangeCollectionView.
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
guard let superr = superview else { return true}
for view in superr.subviews {
if view.isKind(of: OrangeCollectionView.self) {
view.point(inside: point, with: event)
return false
}
}
return true
}
This allows me to scroll the orangeCollectionView HOWEVER this doesn't actually work to fix my issue. I need to be able to scroll left and right to show the cards, however this blocks all touches becuase the point always falls on the OrangeCollectionView.
How can I check to see if they are scrolling left/right to show the cards? Otherwise if they are on the blank cell, scroll the orangeViewController up and down.
Had this issue as well... Didn't find a nice way to do it, but this works.
First, you need to access the scroll view delegate method scrollViewDidScroll(). There you call:
if scrollView == overlayScrollView {
if scrollView.contentOffset.x == self.view.frame.width { // don't know which coordinate you need
self.overlayScrollView.alpa = 0
}
}
After that, you add a blank view onto of the orange collection view. The view's alpha is 0 (I think, maybe the color is just clear; try it out if it works).
In the code, you then add a UISwipeGestureRecognizer to the view you just created and and detect whether there's a swipe to the left or to the right.
By detecting the direction of that swipe, you can simply change the contentOffset.x axis of your overlayScrollView to 0 or self.view.frame.width * 2.
Sorry I can't provide my working sample code, I'm answering from my mobile. It's not the proper solution, but when I made a food app for a big client it worked perfectly and no one ever complained :)

How to align data points in shinobichart based on bars frame in iOS

I'm developing an iOS application and using shinobichart for iOS to display data. But I'm stuck with positioning the datapoints outside or inside the bar based on the bar frame.
I'm stuck in positioning the data point labels inside the bar or outside the bar based on bar frame. If bar is big enough to accommodate the data point I need to position the data point inside bar or else outside the bar as in the below image
In - (void)sChart:(ShinobiChart *)chart alterDataPointLabel:(SChartDataPointLabel *)label forDataPoint:(SChartDataPoint *)dataPoint inSeries:(SChartSeries *)series we need to update data points if we wish as per shinobi documentation. But what logic can help to solve such an alignment of data points.
Also I tried to with the following official shinobi example to align data point aside https://www.shinobicontrols.com/blog/customizable-datapoint-labels
But unable to modify for the requirement.
Please help to solve this riddle.
The best way for you to achieve this is to use the alterDataPointLabel method as you have mentioned. I would do something like this:
func sChart(chart: ShinobiChart!, alterDataPointLabel label: SChartDataPointLabel!, forDataPoint dataPoint: SChartDataPoint!, inSeries series: SChartSeries!) {
if Int(label.text!) > [YOUR CUT OFF VALUE]
{
label.frame.origin.y += 15
label.textColor = UIColor.whiteColor()
}
else
{
label.frame.origin.y -= 15
label.textColor = UIColor.blackColor()
}
}
This works best if you know the values your chart is going to be displayed. However as a more generic way of achieving it I would recommend comparing the y value of the data label's origin against y value of the chart canvas. Then for example you could set the it so it appears within the column if it the series was 90% of the canvas?

Limiting vertical movement of UIAttachmentBehavior inside a UICollectionView

I have a horizontal UICollectionView with a custom UICollectionViewFlowLayout that has a UIAttachmentBehavior set on each cell to give it a bouncy feel when scrolling left and right. The behavior has the following properties:
attachmentBehavior.length = 1.0f;
attachmentBehavior.damping = 0.5f;
attachmentBehavior.frequency = 1.9f;
When a new cell is added to the collection view it's added at the bottom and then animated to its position also using a UIAttachmentBehavior. Naturally it bounces up and down a bit till it rests in its position. Everything is working as expected till now.
The problem I have starts appearing when the collection view is scrolled left or right before the newly added cell has come to rest. The adds left and right bounciness to the up and down one the cell already has from being added. This results in a very weird circular motion in the cell.
My question is, is it possible to stop the vertical motion of a UIAttachmentBehavior while the collection view is being scrolled? I've tried different approaches like using multiple attachment behaviors and disabling scrolling in the collection view till the newly added cell has come to rest, but non of them seem to stop this.
One way to solve this is to use the inherited .action property of the attachment behavior.
You will need to set up a couple of variables first, something like (going from memory, untested code):
BOOL limitVerticalMovement = TRUE;
CGFloat staticCenterY = CGRectGetHeight(self.collectionView.frame) / 2;
Set these as properties of your custom UICollectionViewFlowLayout
When you create your attachment behavior:
UIAttachmentBehavior *attachment = [[UIAttachmentBehavior alloc] initWithItem:item attachedToAnchor:center];
attachment.damping = 1.0f;
attachment.frequency = 1.5f;
attachment.action = ^{
if (!limitVerticalMovement) return;
CGPoint center = item.center;
center.y = staticCenterY;
item.center = center;
};
Then you can turn the limiting function on and off by setting limitVerticalMovement as appropriate.
Have you tried manually removing animations from cells with CALayer's removeAllAnimations?
You'll want to remove the behaviour when the collection view starts scrolling, or perhaps greatly reduce the springiness so that it comes to rest smoothly, but quickly. If you think about it, what you're seeing is a realistic movement for the attachment behaviour you've described.
To keep the vertical bouncing at the same rate but prevent horizontal bouncing, you'd need to add other behaviours - like a collision behaviour with boundaries to the left and right of each added cell. This is going to increase the complexity of the physics a little, and may affect scrolling performance, but it would be worth a try.
Here's how I managed to do it.
The FloatRange limits the range of the attachment, so if you want it to go all the way up and down the screen you just set really large numbers.
This goes inside func recognizePanGesture(sender: UIPanGestureRecognizer) {}
let location = sender.location(in: yourView.superview)
var direction = "Y"
var center = CGPoint(x: 0, y: 0)
if self.direction == "Y" {center.y = 1}
if self.direction == "X" {center.x = 1}
let sliding = UIAttachmentBehavior.slidingAttachment(with: youView, attachmentAnchor: location, axisOfTranslation: CGVector(dx: center.x, dy: center.y))
sliding.attachmentRange = UIFloatRange(minimum: -2000, maximum: 2000)
animator = UIDynamicAnimator(referenceView: self.superview!)
animator.addBehavior(sliding)
If you're using iOS 9 and above then sliding function within attachment class will work perfectly for that job:
class func slidingAttachmentWithItem(_ item: UIDynamicItem,
attachmentAnchor point: CGPoint,
axisOfTranslation axis: CGVector) -> Self
it can be used easily, and it's very effective for sliding Apple documentation
I've resorted to disabling scrolling in the collection view for a specific amount of time after a new cell is added, then removing the attachment behavior after that time has passed using its action property, then adding a new attachment behavior again immediately.
That way I make sure the upwards animation stops before the collection view is scrolled left or right, but also the left/right bounciness is still there when scrolling.
Certainly not the most elegant solution but it works.

How do I move FPPopover as low as I want? If I push it too low, it jumps back to the top

I'm using FPPopover to present a pop over view for my iPhone app. I'm having an issue, however, where when I present it, it will only allow me to present it so low or it will jump to the top. And this is well before it gets cut off anyway.
For example:
[self.speedOptionsPopover presentPopoverFromPoint:CGPointMake(0, 235)];
Works fine, but if I put it a 255 instead of 235 (as it's a good 40px from the bottom) it jumps back up to the top.
Does anyone have any experience with this or how I could fix it?
Also, bonus points if you can explain why the content for the popover always starts like 50px from the top, when I want it to start up higher. How can I change this also?
More code from the creation:
- (void)speedOptionsTapped:(UIBarButtonItem *)sender {
// Set the delegate in the controller that acts as the popover's view to be self so that the controls on the popover can manipulate the WPM and number of words shown
self.speedOptionsController.delegate = self;
self.speedOptionsPopover.arrowDirection = FPPopoverNoArrow;
self.speedOptionsPopover.border = NO;
self.speedOptionsPopover.contentSize = CGSizeMake(320, 190);
[self.speedOptionsPopover presentPopoverFromPoint:CGPointMake(0, 235)];
}
Try replacing this part of the code in FPPopoverController.m:
//ok, will be vertical
if(ht == best_h || self.arrowDirection == FPPopoverArrowDirectionDown)
with this code:
//ok, will be vertical
if (self.arrowDirection == FPPopoverNoArrow)
{
r.origin.x = p.x;
r.origin.y = p.y;
}
else if(ht == best_h || self.arrowDirection == FPPopoverArrowDirectionDown)
The reason you might be having this issue is that the macro FPPopoverArrowDirectionIsVertical considers a popover with no arrows as having a vertical arrow. So, the result is that is tries to best position your popover as close as possible to the view that triggered the popover.
If you replace the code as indicated above, you'll be creating a special case for popovers with no arrows and asking that the original points be respected without repositioning.

Resources