I have a label in my view controller. I am trying to move the position of the label 50 points to the left every time I tap the label. This is what I have so far but my label won't move in the simulation. I do have constraints on the label. It is about 100 wide and 50 in height, and it is also centered in the view controller.
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let tapGesture = UITapGestureRecognizer(target: gestureLabel, action: #selector(moveLabel))
gestureLabel.addGestureRecognizer(tapGesture)
}
func moveLabel(){
if(left){
moveLeft()
}
else{
moveRight()
}
}
func moveLeft(){
let originalX = gestureLabel.frame.origin.x
if(originalX < 0){
bringIntoFrame()
}
else{
gestureLabel.frame.offsetBy(dx: -50, dy: 0)
}
}
here You can move label where ever You want it
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let position = touch.location(in: self.view)
print(position.x)
print(position.y)
lbl.frame.origin = CGPoint(x:position.x-60,y:position.y-50)
}
}
UILabel's userInteractionEnabled is false by default. Try setting it to true
You should be changing the constraint of the label, not the label itself.
Also, your target should be self, not gestureLabel.
Plus, you could animate that. :)
Like so:
class ViewController: UIViewController {
// Centers the Meme horizontally.
#IBOutlet weak var centerHorizontalConstraint: NSLayoutConstraint!
// A custom UIView.
#IBOutlet weak var okayMeme: OkayMeme!
override func viewDidLoad() {
super.viewDidLoad()
addGestureRecognizer()
}
func addGestureRecognizer() {
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(moveMeme))
okayMeme.addGestureRecognizer(tapGesture)
}
func moveMeme() {
UIView.animate(withDuration: 2.0) {
self.centerHorizontalConstraint.constant -= 50
self.view.layoutIfNeeded()
}
}
}
"Demo":
You made a new frame, but didn't assign it to the label. offsetBy doesn't change the frame in place. It creates a new one.
Replace
gestureLabel.frame.offsetBy(dx: -50, dy: 0)
with
gestureLabel.frame = gestureLabel.frame.offsetBy(dx: -50, dy: 0)
But the method above assumes, that you're using directly, not constraints. A standard approach is to have a "Leading to Superview" constraint and change its constant instead of changing the frame.
The label wont move because it has constraints. The easiest way to do this is next:
1) create a label programatically (so you can move it freely around)
var labelDinamic = UILabel(frame: CGRectMake(0, 0,200, 200));
labelDinamic.text = "test text";
self.view.addSubview(labelDinamic);
2) set label initial position (i suggest to use the position of your current label that has constraints. also, hide you constraint label, because you dont need it to be displayed)
labelDinamic.frame.origin.x = yourLabelWithConstraints.frame.origin.x;
labelDinamic.frame.origin.y = yourLabelWithConstraints.frame.origin.y;
3) now you move your label where ever You want with the property
labelDinamic.frame.origin.x = 123
labelDinamic.frame.origin.y = 200
Related
I'm trying to set the origin and width/height of one UIView (red) to a second UIView (blue).
I am calling UIView.frame.origin or size and for some reason the y origin doesn't work.
I've also tried with layout constraints (see it commented out below), but this is overriding my blue fully constrained view.
Then I have a button that animates the red view to the side so you can see the blue view underneath, but I can't get them to line up to start with. Below is my code. In interface builder, I have both UIViews set up as containers. Blue is fully constrained with auto layout and red has no constraints.
import UIKit
class ViewController: UIViewController
{
#IBOutlet weak var blueContainer: UIView!
#IBOutlet weak var redContainer: UIView!
#IBOutlet weak var button: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
print(redContainer.frame)
redContainer.frame.origin.x = view.frame.width/2
redContainer.frame.size.width = view.frame.width
//try to line up y with origin and size
redContainer.frame.origin.y = blueContainer.frame.origin.y
redContainer.frame.size.height = blueContainer.frame.size.height
//also tried by using constraints
//redContainer.topAnchor.constraint(equalTo: blueContainer.topAnchor).isActive = true
//redContainer.heightAnchor.constraint(equalTo: blueContainer.heightAnchor).isActive = true
print(redContainer.frame)
}
#IBAction func slideRed(_ sender: Any) {
if redContainer.frame.origin.x == 0 {
UIView.animate(withDuration: 0.5) {
self.redContainer.frame.origin.x = self.view.frame.width/2
}
button.setTitle("Come Back Red!", for: .normal)
} else {
UIView.animate(withDuration: 0.5) {
self.redContainer.frame.origin.x = 0
}
button.setTitle("Go Away Red!", for: .normal)
}
}
}
ViewDidLoad does not guarantee the view has laid out its constraints. So when blueContainer's frame and size is zero, you will not see any effect on redContainer. You should use viewDidLayoutSubviews to get the correct frame and size from blueContainer.
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
redContainer.frame.origin.x = view.frame.width/2
redContainer.frame.size.width = view.frame.width
//try to line up y with origin and size
redContainer.frame.origin.y = blueContainer.frame.origin.y
redContainer.frame.size.height = blueContainer.frame.size.height
}
I've got the following structure for example:
I want to rotate my label by 270degrees to achieve this:
via CGAffineTransform.rotated next way:
credentialsView.text = "Developed in EVNE Developers"
credentialsView.transform = credentialsView.transform.rotated(by: CGFloat(Double.pi / 2 * 3))
but instead of expected result i've got the following:
So, what is the correct way to rotate view without changing it's bounds to square or whatever it does, and keep leading 16px from edge of screen ?
I tried a lot of ways, including extending of UILabel to see rotation directly in storyboard, putted dat view in stackview with leading and it also doesn't helps, and etc.
Here is the solution which will rotate your label in an appropriate way forth and back to vertical-horizontal state. Before running the code, set constraints for your label in storyboard: leading to 16 and vertically centered.
Now check it out:
class ViewController: UIViewController {
#IBOutlet weak var label: UILabel!
// Your leading constraint from storyboard, initially set to 16
#IBOutlet weak var leadingConstraint: NSLayoutConstraint!
var isHorizontal: Bool = true
var defaultLeftInset: CGFloat = 16.0
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .white
label.text = "This is my label"
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(tapAction)))
}
#objc func tapAction() {
if self.isHorizontal {
// Here goes some magic
// constraints do not depend on transform matrix,
// so we have to adjust a leading one to fit our requirements
leadingConstraint.constant = defaultLeftInset - label.frame.width/2 + label.frame.height/2
self.label.transform = CGAffineTransform(rotationAngle: .pi/2*3)
}
else {
leadingConstraint.constant = defaultLeftInset
self.label.transform = .identity
}
self.isHorizontal = !self.isHorizontal
}
}
I have a view in which i am creating another view programmatically on add button through xib. I am able to create multiple views on tapping add more button and remove is also working if I remove last view but the problem occurs with middle views due to missing constraints view not updating correctly bellow are images how it is looking
At start view Look like this
After Adding more view
After removing middle view
Delete button code
#IBAction func deletebnt(_ sender: UIButton) {
let view = self.superview
let index = view?.subviews.index(of:self)!
delegate.txtcheck(text: countstr)
self.view.removeFromSuperview()
}
Add button Code
#IBAction func addMoreBnt(_ sender: UIButton) {
for constraint in addSuperview.constraints {
if constraint.firstAttribute == NSLayoutAttribute.height
{
constraint.constant += 45
space = constraint.constant
}
}
let newView : AvalabileTimeView = AvalabileTimeView()
newView.frame = CGRect(x: self.addsubView.frame.origin.x, y: 70, width: addsubView.frame.size.width, height:addsubView.frame.size.height)
newView.delegate = self as AvalabileTimeDelegate
addSuperview.addSubview(newView)
let index = addSuperview.subviews.index(of: newView)!
newView.translatesAutoresizingMaskIntoConstraints = false
let heightConstraint = newView.widthAnchor.constraint(equalToConstant:addsubView.frame.size.width )
let widthConstaint = newView.heightAnchor.constraint(equalToConstant:31 )
let topConstraint = newView.topAnchor.constraint(equalTo: addSuperview.topAnchor, constant: space - 31) NSLayoutConstraint.activate([heightConstraint,topConstraint,widthConstaint])
}
delegate to change height of superview
func txtcheck(text: String!) {
print(text)
for constraint in addSuperview.constraints {
if constraint.firstAttribute == NSLayoutAttribute.height
{
constraint.constant -= 45
// Here I have to set constraint for bottom view and topview of deleted view but I don't know how to do
}
}
}
Here is link to demo project
https://github.com/logictrix/addFieldDemo
Instead of adding each new AvalabileTimeView with its own constraints, use a UIStackView - you can remove almost all of your existing code for adding / removing the new views.
Look at .addArrangedSubview() and .removeArrangedSubview()
Here is some sample code... you'll need to add a UIStackView in Interface Builder, connect it to the Outlet, and adjust the constraints, but that's about all:
// in ViewController.swift
#IBOutlet weak var availableTimeStackView: UIStackView!
#IBAction func addMoreBnt(_ sender: UIButton) {
// instantiate a new AvalabileTimeView
let newView : AvalabileTimeView = AvalabileTimeView()
// set its delegate to self
newView.delegate = self as AvalabileTimeDelegate
// add it to the Stack View
availableTimeStackView.addArrangedSubview(newView)
// standard for auto-layout
newView.translatesAutoresizingMaskIntoConstraints = false
// only constraint needed is Height (width and vertical spacing handled by the Stack View)
newView.heightAnchor.constraint(equalToConstant: 31).isActive = true
}
// new delegate func
func removeMe(_ view: AvalabileTimeView) {
// remove the AvalabileTimeView from the Stack View
availableTimeStackView.removeArrangedSubview(view)
}
// in AvalabileTimeView.swift
protocol AvalabileTimeDelegate{
// don't need this anymore
func txtcheck(text: String!)
// new delegate func
func removeMe(_ view: AvalabileTimeView)
}
#IBAction func deletebnt(_ sender: UIButton) {
delegate.removeMe(self)
}
I want to place programmatically a view at the center of all the the subviews created in a storyboard.
In the storyboard, I have a view, and inside a Vertical StackView, which has constraint to fill the full screen, distribution "Equal spacing".
Inside of the Vertical Stack View, I have 3 horizontal stack views, with constraint height = 100, and trailing and leading space : 0 from superview. The distribution is "equal spacing" too.
In each horizontal stack view, I have two views, with constraint width and height = 100, that views are red.
So far, so good, I have the interface I wanted,
Now I want to retrieve the center of each red view, to place another view at that position (in fact, it'll be a pawn over a checkboard...)
So I wrote that:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var verticalStackView:UIStackView?
override func viewDidLoad() {
super.viewDidLoad()
print ("viewDidLoad")
printPos()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
print ("viewWillAppear")
printPos()
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
print ("viewWillLayoutSubviews")
printPos()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
print ("viewDidLayoutSubviews")
printPos()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
print ("viewDidAppear")
printPos()
addViews()
}
func printPos() {
guard let verticalSV = verticalStackView else { return }
for horizontalSV in verticalSV.subviews {
for view in horizontalSV.subviews {
let center = view.convert(view.center, to:nil)
print(" - \(center)")
}
}
}
func addViews() {
guard let verticalSV = verticalStackView else { return }
for horizontalSV in verticalSV.subviews {
for redView in horizontalSV.subviews {
let redCenter = redView.convert(redView.center, to:self.view)
let newView = UIView(frame: CGRect(x:0, y:0, width:50, height:50))
//newView.center = redCenter
newView.center.x = 35 + redCenter.x / 2
newView.center.y = redCenter.y
newView.backgroundColor = .black
self.view.addSubview(newView)
}
}
}
}
With that, I can see that in ViewDidLoad and ViewWillAppear, the metrics are those of the storyboard. The positions changed then in viewWillLayoutSubviews, in viewDidLayoutSubviews and again in viewDidAppear.
After viewDidAppear (so after all the views are in place), I have to divide x coordinate by 2 and adding something like 35 (see code) to have the new black view correctly centered in the red view. I don't understand why I can't simply use the center of the red view... And why does it works for y position ?
I found your issue, replace
let redCenter = redView.convert(redView.center, to:self.view)
with
let redCenter = horizontalSV.convert(redView.center, to: self.view)
When you convert, you have to convert from the view original coordinates, here it was the horizontalSv
So you want something like this:
You should do what Beninho85 and phamot suggest and use constraints to center the pawn over its starting square. Note that the pawn does not have to be a subview of the square to constrain them together, and you can add the pawn view and its constraints in code instead of in the storyboard:
#IBOutlet private var squareViews: [UIView]!
private let pawnView = UIView()
private var pawnSquareView: UIView?
private var pawnXConstraint: NSLayoutConstraint?
private var pawnYConstraint: NSLayoutConstraint?
override func viewDidLoad() {
super.viewDidLoad()
let imageView = UIImageView(image: #imageLiteral(resourceName: "Pin"))
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.contentMode = .scaleAspectFit
pawnView.addSubview(imageView)
NSLayoutConstraint.activate([
imageView.centerXAnchor.constraint(equalTo: pawnView.centerXAnchor),
imageView.centerYAnchor.constraint(equalTo: pawnView.centerYAnchor),
imageView.widthAnchor.constraint(equalTo: pawnView.widthAnchor, multiplier: 0.8),
imageView.heightAnchor.constraint(equalTo: pawnView.heightAnchor, multiplier: 0.8)])
pawnView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(pawnView)
let squareView = squareViews[0]
NSLayoutConstraint.activate([
pawnView.widthAnchor.constraint(equalTo: squareView.widthAnchor),
pawnView.heightAnchor.constraint(equalTo: squareView.heightAnchor)])
constraintPawn(toCenterOf: squareView)
let dragger = UIPanGestureRecognizer(target: self, action: #selector(draggerDidFire(_:)))
dragger.maximumNumberOfTouches = 1
pawnView.addGestureRecognizer(dragger)
}
Here are the helper functions for setting up the x and y constraints:
private func constraintPawn(toCenterOf squareView: UIView) {
pawnSquareView = squareView
self.replaceConstraintsWith(
xConstraint: pawnView.centerXAnchor.constraint(equalTo: squareView.centerXAnchor),
yConstraint: pawnView.centerYAnchor.constraint(equalTo: squareView.centerYAnchor))
}
private func replaceConstraintsWith(xConstraint: NSLayoutConstraint, yConstraint: NSLayoutConstraint) {
pawnXConstraint?.isActive = false
pawnYConstraint?.isActive = false
pawnXConstraint = xConstraint
pawnYConstraint = yConstraint
NSLayoutConstraint.activate([pawnXConstraint!, pawnYConstraint!])
}
When the pan gesture starts, deactivate the existing x and y center constraints and create new x and y center constraints to track the drag:
#objc private func draggerDidFire(_ dragger: UIPanGestureRecognizer) {
switch dragger.state {
case .began: draggerDidBegin(dragger)
case .cancelled: draggerDidCancel(dragger)
case .ended: draggerDidEnd(dragger)
case .changed: draggerDidChange(dragger)
default: break
}
}
private func draggerDidBegin(_ dragger: UIPanGestureRecognizer) {
let point = pawnView.center
dragger.setTranslation(point, in: pawnView.superview)
replaceConstraintsWith(
xConstraint: pawnView.centerXAnchor.constraint(equalTo: pawnView.superview!.leftAnchor, constant: point.x),
yConstraint: pawnView.centerYAnchor.constraint(equalTo: pawnView.superview!.topAnchor, constant: point.y))
}
Note that I set the translation of the dragger so that it is exactly equal to the desired center of the pawn view.
If the drag is cancelled, restore the constraints to the starting square's center:
private func draggerDidCancel(_ dragger: UIPanGestureRecognizer) {
UIView.animate(withDuration: 0.2) {
self.constraintPawn(toCenterOf: self.pawnSquareView!)
self.view.layoutIfNeeded()
}
}
If the drag ends normally, set constraints to the nearest square's center:
private func draggerDidEnd(_ dragger: UIPanGestureRecognizer) {
let squareView = nearestSquareView(toRootViewPoint: dragger.translation(in: view))
UIView.animate(withDuration: 0.2) {
self.constraintPawn(toCenterOf: squareView)
self.view.layoutIfNeeded()
}
}
private func nearestSquareView(toRootViewPoint point: CGPoint) -> UIView {
func distance(of candidate: UIView) -> CGFloat {
let center = candidate.superview!.convert(candidate.center, to: self.view)
return hypot(point.x - center.x, point.y - center.y)
}
return squareViews.min(by: { distance(of: $0) < distance(of: $1)})!
}
When the drag changes (meaning the touch moved), update the constants of the existing constraint to match the translation of the gesture:
private func draggerDidChange(_ dragger: UIPanGestureRecognizer) {
let point = dragger.translation(in: pawnView.superview!)
pawnXConstraint?.constant = point.x
pawnYConstraint?.constant = point.y
}
You lose your time with frames. Because there is AutoLayout, frames are moved and with frames you can add your views but it's too late in the ViewController lifecycle. Much easier and effective to use constraints/anchors, just take care to have views in the same hierarchy:
let newView = UIView()
self.view.addSubview(newView)
newView.translatesAutoresizingMaskIntoConstraints = false
newView.centerXAnchor.constraint(equalTo: self.redView.centerXAnchor).isActive = true
newView.centerYAnchor.constraint(equalTo: self.redView.centerYAnchor).isActive = true
newView.widthAnchor.constraint(equalToConstant: 50.0).isActive = true
newView.heightAnchor.constraint(equalToConstant: 50.0).isActive = true
But to answer the initial problem maybe it was due to the fact that there is the stackview between so maybe coordinates are relative to the stackview instead of the container or something like that.
You need not divide the height and width of the coordinates to get the center of the view. You can use align option by selecting multiple views and add horizontal centers and vertical centers constraints.
On Facebook Messenger for iOS, it has it so if the keyboard is hidden, if you swipe up, the keyboard will show. It does this exactly in reverse to the interactive keyboard dismissal mode: the keyboard reveals itself as you swipe up at the speed in which you swipe up.
Does anybody have any pointers on how to do this?
Edit: thanks for the answers! I was mostly looking into whether there was a built-in way to do this, since I saw it being done in Facebook Messenger. However, I just read a blog post where they said they had to screenshot the system keyboard to get the effect—so I’ll assume there’s no built-in way to do this! As mentioned in the comments, I’m using a custom keyboard, so this should be a lot easier, since I have control over the frame!
Basically you'll need UIPanGestureRecognizer.
Set UIScreenEdgePanGestureRecognizer for bottom edge, UIPanGestureRecognizer for hiding the keyboard in Storyboard and drag #IBAction outlets to the code.
Set you keyboard view container with your keyboard in the bottom of the controller in Storyboard, so that user doesn't see it. Drag an #IBOutlet to your code so that you'll be able to modify it's frame.
In gesture actions when dragging your animate the view movement.
When stopped dragging you need to check the view's position and animate it to the destination if it's not there yet.
Also you'll need to add a check for the dragging area so that user cannot drag it further.
It's simple, you'll just need to check all cases and test it properly.
This is a basic setup you can build from this:
class ViewController: UIViewController {
#IBOutlet weak var keyboardContainerView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func onEdgePanGestureDrag(_ sender: UIScreenEdgePanGestureRecognizer) {
let point = sender.location(in: view)
view.layoutIfNeeded()
UIView.animate(withDuration: 0.33) {
// Animate your custom keyboard view's position
self.keyboardContainerView.frame = CGRect(x: self.keyboardContainerView.bounds.origin.x,
y: point.y,
width: self.keyboardContainerView.bounds.width,
height: self.keyboardContainerView.bounds.height)
}
view.layoutIfNeeded()
}
#IBAction func onPanGestureDrag(_ sender: UIPanGestureRecognizer) {
let point = sender.location(in: view)
view.layoutIfNeeded()
UIView.animate(withDuration: 0.33) {
// Animate your custom keyboard view's position
self.keyboardContainerView.frame = CGRect(x: self.keyboardContainerView.bounds.origin.x,
y: point.y,
width: self.keyboardContainerView.bounds.width,
height: self.keyboardContainerView.bounds.height)
}
view.layoutIfNeeded()
}
}
Here is an implementation that worked for me. It's not perfect but it works fairly well and is simple to implement.You will need a collection view or a table view.
For the sake of simplicity I will only show code that is necessary for this feature. so please handle everything else that is necessary for the views' initialization.
class ViewController: UIViewController {
var collectionView: UICollectionView!
var textView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
collectionView.panGestureRecognizer.addTarget(self, action: #selector(handlePan(_:)))
collectionView.keyboardDismissMode = .interactive
// Only necessary for empty collectionView
collectionView.alwaysBounceVertical = true
}
func handlePan(_ sender: UIPanGestureRecognizer) {
if sender.state == .changed {
let translation = sender.translation(in: view)
if translation.y < 0 && collectionView.isAtBottom && !self.textView.isFirstResponder() {
self.textView.becomeFirstResponder()
}
}
}
}
extension UIScrollView {
var isAtBottom: Bool {
return contentOffset.y >= verticalOffsetForBottom
}
var verticalOffsetForBottom: CGFloat {
let scrollViewHeight = bounds.height
let scrollContentSizeHeight = contentSize.height
let bottomInset = contentInset.bottom
let scrollViewBottomOffset = scrollContentSizeHeight + bottomInset - scrollViewHeight
return scrollViewBottomOffset
}
}