How I can restrict position view while moving/dragging on PDFView? (Swift) - ios

I took one custom view (SignatoryView) on pdf, When I click on Add button from Navigation bar, it will add that SignatoryView on PDF view and according to my choice I can move/drag that signatory view to any location.
Problem: When I am moving that signatory view on pdf, it is going outside edges of pdfView. (Left, right, bottom and top also)
It should not go beyond its boundaries, it should be movable only inside edges on PDF view.
How I can achieve this ? Here is the complete project code

You just need to get half of the width and half of the height of your signature and when setting the new center position of it add or subtract it from the origin x and/or y:
let minX = frame!.width/2
let minY = frame!.height/2
let maxX = pdfView.frame.width-minX
let maxY = pdfView.frame.height-minY
customView1?.center = CGPoint(
x: min(maxX, max(minX, touchLocation!.x)),
y: min(maxY ,max(minY, touchLocation!.y)))

Related

How do i get the vertical distance from an UIView to another one?

As the title suggests, i struggle to find a way to calculate the vertical distance between UIViews.
Say i have two buttons in a View Controller. How would I go about getting a CGFloat indicative of the distance between button1's bottom and button2's top?
As already pointed out by #lobstah, the difference can be calculated by accessing the frames of the views in question.
However, there's an important detail that must be considered: This only works in that simple way, if the views share the same parent view. Because only then, the frames will be expressed with regards to the same coordinate space.
A general approach with doesn't come with this restriction and will always work as long as the views are part of the same view hierarchy (don't appear on different windows) and are not scaled / rotated by an affine transform is the following:
func distanceBetween(bottomOf view1: UIView, andTopOf view2: UIView) -> CGFloat {
let frame2 = view1.convert(view2.bounds, from: view2)
return frame2.minY - view1.bounds.maxY
}
The distance would be positive if the top of view2 is below the bottom of view1.
You can use maxY of the top and minY of bottom view’s frames properties:
bottomView.frame.minY - topView.frame.maxY

Swift 4 reverse change height of a UIView

I would like to build a progressbar. I took a rectangular view (progressBarBckgr) and put another one in front of it (progressBar).
The one in the front should increase its height by tabbing on the correct answer within a quiz app.
func updateUI () {
let numberOfAllQuestions = allQuestion.questionList.count
print(numberOfAllQuestions) //prints 3
let progressBarBckgrHeight = progressBarBckgr.frame.size.height
let progressBarBckgrHeightInt = Int(progressBarBckgrHeight)
let progressBarBckgrHeightPiece = progressBarBckgrHeightInt / numberOfAllQuestions
progressBarOutlet.frame.size.height = (progressBarBckgr.frame.height) + CGFloat (progressBarBckgrHeightPiece)
}
So with every correct clicked answer the progressbar should increase just so far, that in the end of quiz the whole backgroundprogressbar is covered.
Example:
10 Questions
-> clicked answer fills increases the progressbar's height for 1/10
In addition to that i would like to increase the bar from the bottom to the top. As i enter the property height to increase it, it only gets bigger in direction bottom. Is there a nice turnaround trick?
Thanks!!!
You also need to move the view up by the amount of height you have increased (progressBarBckgrHeightPiece)
Also, you should set the frame directly.
progressBarOutlet.frame = CGRect(x: progressBarOutlet.frame.origin.x,
y: progressBarOutlet.frame.origin.y - progressBarBckgrHeightPiece, // Notice here
width: progressBarOutlet.frame.width,
height: progressBarOutlet.frame.height + CGFloat(progressBarBckgrHeightPiece))

How to animate centered square to the top

I have a UIView in a portrait-only app.
The view is centered vertically and horizontally with AutoLayout ("manually" using storyboards).
The width equals the (main)view.width * 0.9
The height is the same size of the width (it is a square).
I want to tap a button inside this UIView and animate it only vertically until it reaches the top border of the screen (eg. height*0.9, 10 pts, whatever is possible).
When I click again, I want to reposition back the view to its original position (centered as it was when I first tapped).
During the transition the square should not be tappable.
After reading many posts I could not understand what's the best way to do this (I red mainly developers saying old techniques using centerX should be avoided and lamentations about some versions of the SO behaving in strange ways).
I suppose I should find a way to get the current "position" of the constraints and to assign a constraint the "final" position, but I was not able to do it.
Any help is appreciated
You are all going the wrong way about this.
Add one constraint that pins the view to the top, and add one constraint that pins the view to centerY. It will complain, so pick one and disable it (I think the property in Interface Builder is called Installed).
If the initial state is the view in the center, disable the constraint that pins it to the top, and viceversa.
Now write IBOutlets for both constraints in your controller and connect them to those constraints. Make sure the declaration of that variable is not weak, otherwise the variable will become nil when the constraint is disabled.
Whenever you want to toggle your animation you can enable one constraint and disable the other.
#IBOutlet var topConstraint: NSLayoutConstraint!
#IBOutlet var centerConstraint: NSLayoutConstraint!
func toggleState(moveToTop: Bool) {
UIView.animateWithDuration(0.25) {
self.topConstraint.isActive = moveToTop
self.centerConstraint.isActive = !moveToTop
self.view.layoutIfNeeded()
}
}
While you can use Autolayout to animate - to take the constraint constraining the centerY and set its constant to a value that would move to the top (e.g., constant = -(UIScreen.main.bounds.height / 2)), I would recommend using view's transform property.
So to move the view to the top you can use:
let topMargin = CGFloat(20)
let viewHalfHeight = self.view.bounds.height / 2
let boxHalfHeight = self.box.bounds.height / 2
UIView.animate(withDuration: 0.2) {
box.transform = CGAffineTransform.identity
.translatedBy(x: 0, y: -(viewHalfHeight - (boxHalfHeight + topMargin)))
}
You are moving box.center related to the view.center - so if you want to move the box to the top, you have to move its center by half a view's height (because the view's centerY is exactly height / 2 far from the view's top). That is not enough though, because then only a bottom half of the box is visible (now the box.centerY == view.top). Therefore you have to move it back by the box.bounds.height / 2 (in my code boxHalfHeight) - to make the top half visible. And to that boxHalfHeight you add topMargin so that there is some margin to the top.
Then, to move the box back to original position:
UIView.animate(withDuration: 0.2) {
box.transform = CGAffineTransform.identity
}
EDIT
If you really want to go with autolayout, you have to have a reference to the centerY constraint, so for example if it is created this way:
let boxCenterYConstraint = self.box.centerYAnchor.constraint(equalTo: self.view.centerYAnchor)
boxCenterYConstraint.isActive = true
Then you can try this:
// calculating the translation is the same
let topMargin = CGFloat(20)
let viewHalfHeight = self.view.bounds.height / 2
let boxHalfHeight = self.box.bounds.height / 2
let diff = -(viewHalfHeight - (boxHalfHeight + topMargin))
boxCenterYConstraint.constant = diff
self.view.setNeedsLayout()
UIView.animate(withDuration: 0.2) {
self.view.layoutIfNeeded()
}
And animation back:
boxCenterYConstraint.constant = 0
self.view.setNeedsLayout()
UIView.animate(withDuration: 0.2) {
self.view.layoutIfNeeded()
}

New view.frame.y doesn't match up with label.frame.y

I have a label on-screen that has its height set as 300px, set to the bottom of the screen, and I want to create a UIView that exactly overlays it. The code I use is
let theFrame = CGRect(x: myLabel.frame.minX, y: myLabel.frame.minY, width: myLabel.frame.width, height: myLabel.frame.height)
newVu = UIView(frame: theFrame)
let newColor = UIColor(colorLiteralRed: 0.5, green: 0.5, blue: 1, alpha: 0.5)
newVu.backgroundColor = newColor
self.view.addSubview(newVu)
The result that I get has the new UIView shifted about 20 points higher than the label. ((EDIT: as GeorgeGreen helped me see in Comments, this is because the label is within a Vertical StackView, which is constrained to the bottom of the Top Layout Guide -- thus the Y of the label is relative to the top of the Stack View, not the top of the screen.)) Clearly there are different frames of reference at work, but what is needed to bring everything into the same frame of reference? EDIT: Or, asked another way, how can I get the "absolute" X and Y coordinates of the label in the screen, so that no matter how many views it is embedded in, I can know where to drop a new View to exactly overlay it?
Original screen in IB:
Result of the code (new view is semi-transparent; note the yellow band at the bottom):
You can get your label position in view by
let theFrame = view.convert(myLabel.frame, from: your_stack_view_here)
and You should move it to
viewDidAppear
Because
viewWillLayoutSubviews
will call any time subview will layout. That's mind at the first time it run, your "mylabel" didn't layout with correct position.
Apple document
Regarding your question of “how can I get the "absolute" X and Y coordinates of the label in the screen”, the answer is to use UIView.convert(_:to:), passing nil as the second parameter. (This will yield coordinates relative to the containing window's origin, but for practical purposes is probably what you mean by “the screen”).

How can I tap on a UICollectionView cell of a collection view in UIAutomation?

How can I tap on a UICollectionView cell of a collection view in UIAutomation?
I tried this
var iconsCollView = window.collectionViews()[0];
var iconRect = iconsCollView.cells()[0].rect();
var iconX = iconRect.origin.x/100;
var iconY = iconRect.origin.y/100;
iconsCollView.tapWithOptions({tapOffset:{x: iconX, y: iconY}});
but it taps another cell in the collection view, a wrong cell other than the cell I specified its offset.
Can you please help me? is there another way?
From the UIAElement class reference:
Your script should treat the rect object as a generic JavaScript object whose properties for origin, x, y, size, width, and height correspond to those of the analogous CGRect Cocoa structure. The rect object has the form {origin:{x:xposition,y:yposition}, size:{width:widthvalue, height:heightvalue}}. The relevant coordinates are screen-relative and are adjusted to account for device orientation.
From the same source under tapWithOptions method:
You can use offsets to achieve finer precision in specifying the hitpoint within the rect for the specified element. The offset comprises a pair of x and y values, each ranging from 0.0 to 1.0. These values represent, respectively, relative horizontal and vertical positions within the rect, with {x:0.0, y:0.0} as the top left and {x:1.0, y:1.0} as the bottom right. Thus, {x:0.3, y:0.6} specifies a position just below and to the left of center, and {x:1.0, y:0.5} specifies a position centered vertically at the far right.
From the source you provided, you're trying to tap inside of collection view passing some weirdly scaled offset coordinates of one of its cells (instead of expected relative horizontal and vertical positions within the rect of collection view).
If you want to tap the cell simply locate it and call the tap method on it:
var iconsCollView = window.collectionViews()[0];
var iconCellToTap = iconsCollView.cells()[0].tap();

Resources