How do i move the UILabel Depending on UISlider - ios

How do I change the origin y of the UILabel depending on UISlider?
If the slider value is more than 1.5 labels, it should move back to its position or move down. I want to change the y origin depending on the scroll of UISlider value.
Until now I have tried this but it's not effective.
The below function is valueChanged method of UISlider:
internal func valueChanged(value: Float) {
if value > 1.0 && value < 1.5 {
let view = self.viewWithTag(400) as ! UIImageView
view.transform = CGAffineTransformScale(CGAffineTransformIdentity, CGFloat(value - 0.5), CGFloat(value - 0.5))
let label = self.viewWithTag(500) as ! UILabel
if value > lastValue {
label.frame.origin.y = (label.frame.origin.y + CGFloat(value - 0.5))
}
if value < lastValue {
label.frame.origin.y = (label.frame.origin.y - CGFloat(value - 0.5))
}
} else {
UIView.animateWithDuration(0.1) {
let view = self.viewWithTag(400) as ! UIImageView
view.transform = CGAffineTransformScale(CGAffineTransformIdentity, CGFloat(1.0), CGFloat(1.0))
}
}
}
I know it's a wrong approach.

Related

How to convert CATransform3DTranslate to CGAffineTransform so it can Mimic a Carousel View

My question pertains to how to mimic this Carousel view Youtube video only using a UIView not it's layer or a CALayer, which means actually transforming the UIViews its self.
I found a stack overflow question that actually is able to convert a
CATransform3D into a CGAffineTransform. That was written by some genius here as an answer, but my problem is a little unique.
The animation you see below is using CALayer to create. I need to create this same animation but transforming the UIView instead of its layer.
What it's Supposed to look like:
Code (Creates animation using Layers):
This takes an image card which is a CALayer() with a image attached to it and transforms which places it in the Carousel of images.
Note: turnCarousel() is also called when the user pans which moves / animates the Carousel.
let transformLayer = CATransformLayer()
func turnCarousel() {
guard let transformSubLayers = transformLayer.sublayers else {return}
let segmentForImageCard = CGFloat(360 / transformSubLayers.count)
var angleOffset = currentAngle
for layer in transformSubLayers {
var transform = CATransform3DIdentity
transform.m34 = -1 / 500
transform = CATransform3DRotate(transform, degreeToRadians(deg: angleOffset), 0, 1, 0)
transform = CATransform3DTranslate(transform, 0, 0, 175)
CATransaction.setAnimationDuration(0)
layer.transform = transform
angleOffset += segmentForImageCard
}
}
What It Currently Looks Like:
So basically it's close, but it seems as though there is a scaling issue with the cards that are supposed to be seen as in the front and the cards that are in the back of the carousel.
Fo this what I did is use a UIImageView as the base view for the carousel and then added more UIImageViews as cards to it. So now we are trying do a transformation on a UIImageView/UIView
Code:
var carouselTestView = UIImageView()
func turnCarouselTestCarousel() {
let segmentForImageCard = CGFloat(360 / carouselTestView.subviews.count)
var angleOffset = currentAngleTestView
for subview in carouselTestView.subviews {
var transform2 = CATransform3DIdentity
transform2.m34 = -1 / 500
transform2 = CATransform3DRotate(transform2, degreeToRadians(deg: angleOffset), 0, 1, 0)
transform2 = CATransform3DTranslate(transform2, 0, 0, 175)
CATransaction.setAnimationDuration(0)
// m13, m23, m33, m43 are not important since the destination is a flat XY plane.
// m31, m32 are not important since they would multiply with z = 0.
// m34 is zeroed here, so that neutralizes foreshortening. We can't avoid that.
// m44 is implicitly 1 as CGAffineTransform's m33.
let fullTransform: CATransform3D = transform2
let affine = CGAffineTransform(a: fullTransform.m11, b: fullTransform.m12, c: fullTransform.m21, d: fullTransform.m22, tx: fullTransform.m41, ty: fullTransform.m42)
subview.transform = affine
angleOffset += segmentForImageCard
}
}
the sub image that actually make up the Carousel are add with this function which simply goes through a for loop of image named 1...6 in my assets folder.
Code:
func CreateCarousel() {
carouselTestView.frame.size = CGSize(width: self.view.frame.width, height: self.view.frame.height / 2.9)
carouselTestView.center = CGPoint(self.view.frame.width * 0.5, self.view.frame.height * 0.5)
carouselTestView.alpha = 1.0
carouselTestView.backgroundColor = UIColor.clear
carouselTestView.isUserInteractionEnabled = true
self.view.insertSubview(carouselTestView, at: 0)
for i in 1 ... 6 {
addImageCardTestCarousel(name: "\(i)")
}
// Set the carousel for the first time. So that now we can see it like an actual carousel animation
turnCarouselTestCarousel()
let panGestureRecognizerTestCarousel = UIPanGestureRecognizer(target: self, action: #selector(self.performPanActionTestCarousel(recognizer:)))
panGestureRecognizerTestCarousel.delegate = self
carouselTestView.addGestureRecognizer(panGestureRecognizerTestCarousel)
}
The addImageCardTestCarousel function is here:
Code:
func addImageCardTestCarousel(name: String) {
let imageCardSize = CGSize(width: carouselTestView.frame.width / 2, height: carouselTestView.frame.height)
let cardPanel = UIImageView()
cardPanel.frame.size = CGSize(width: imageCardSize.width, height: imageCardSize.height)
cardPanel.frame.origin = CGPoint(carouselTestView.frame.size.width / 2 - imageCardSize.width / 2 , carouselTestView.frame.size.height / 2 - imageCardSize.height / 2)
guard let imageCardImage = UIImage(named: name) else {return}
cardPanel.image = imageCardImage
cardPanel.contentMode = .scaleAspectFill
cardPanel.layer.masksToBounds = true
cardPanel.layer.borderColor = UIColor.white.cgColor
cardPanel.layer.borderWidth = 1
cardPanel.layer.cornerRadius = cardPanel.frame.height / 50
carouselTestView.addSubview(cardPanel)
}
Purpose:
The purpose of this is that I want to build a UI that can take UIViews on the rotating cards you see, and a CALayer cannot add a UIView as a subview. It can only add the UIView's layer to its own Layer. So to solve this problem I need to actually achieve this animation with UIViews not CALayers.
I solved it the view that Appears to be behind the front most view are actually grabbing all the touch even if you touch a card right in the front the back card will prevent touches for the front card. So i made a function that can calculate. Which views are in the front. Than disable and enable touches for then. Its like when 2 cards get stacked on top of each other the card to the back will stop the card from the front from taking/userInteraction.
Code:
func DetermineFrontViews(view subview: UIView, angle angleOffset: CGFloat) {
let looped = Int(angleOffset / 360) // must round down to Int()
let loopSubtractingReset = CGFloat(360 * looped) // multiply 360 how ever many times we have looped
let finalangle = angleOffset - loopSubtractingReset
if (finalangle >= -70 && finalangle <= 70) || (finalangle >= 290) || (finalangle <= -260) {
print("In front of view")
if subview.isUserInteractionEnabled == false {
subview.isUserInteractionEnabled = true
}
} else {
print("Back of view")
if subview.isUserInteractionEnabled == true {
subview.isUserInteractionEnabled = false
}
}
}
I added this to the turn function to see if it could keep track of the first card being either in the back of the carousel or the front.
if subview.layer.name == "1" {
DetermineFrontViews(view: subview, angle: angleOffset)
}

How to make infinity scroll on Scroll View in Xcode project?

I have a Scrollview in my app that shows a few slides,
the default behaviour of the slides are when you reach the last one (ie slide 5 for example) is that it doesn't scroll more to the right,
or when you go to the first one it doesn't scroll more to the left.
how do I make it infinity loop , meaning when I see the last slide, and I scroll more right, it will just scroll to the first one?
(or from the first one left it will scroll to the last one)
here is my scrollViewDidScroll:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let pageIndex = round(scrollView.contentOffset.x/view.frame.width)
pageControl.currentPage = Int(pageIndex)
let maximumHorizontalOffset: CGFloat = scrollView.contentSize.width - scrollView.frame.width
let currentHorizontalOffset: CGFloat = scrollView.contentOffset.x
// vertical
let maximumVerticalOffset: CGFloat = scrollView.contentSize.height - scrollView.frame.height
let currentVerticalOffset: CGFloat = scrollView.contentOffset.y
let percentageHorizontalOffset: CGFloat = currentHorizontalOffset / maximumHorizontalOffset
let percentageVerticalOffset: CGFloat = currentVerticalOffset / maximumVerticalOffset
/*
* below code changes the background color of view on paging the scrollview
*/
// self.scrollView(scrollView, didScrollToPercentageOffset: percentageHorizontalOffset)
/*
* below code scales the imageview on paging the scrollview
*/
let percentOffset: CGPoint = CGPoint(x: percentageHorizontalOffset, y: percentageVerticalOffset)
print ("present offset: ", percentOffset.x)
if(percentOffset.x > 0 && percentOffset.x <= 0.25) {
slides[0].mainPic.transform = CGAffineTransform(scaleX: (0.25-percentOffset.x)/0.25, y: (0.25-percentOffset.x)/0.25)
slides[1].mainPic.transform = CGAffineTransform(scaleX: percentOffset.x/0.25, y: percentOffset.x/0.25)
} else if(percentOffset.x > 0.25 && percentOffset.x <= 0.50) {
slides[1].mainPic.transform = CGAffineTransform(scaleX: (0.50-percentOffset.x)/0.25, y: (0.50-percentOffset.x)/0.25)
slides[2].mainPic.transform = CGAffineTransform(scaleX: percentOffset.x/0.50, y: percentOffset.x/0.50)
} else if(percentOffset.x > 0.50 && percentOffset.x <= 0.75) {
slides[2].mainPic.transform = CGAffineTransform(scaleX: (0.75-percentOffset.x)/0.25, y: (0.75-percentOffset.x)/0.25)
slides[3].mainPic.transform = CGAffineTransform(scaleX: percentOffset.x/0.75, y: percentOffset.x/0.75)
} else if(percentOffset.x > 0.75 && percentOffset.x <= 1) {
slides[3].mainPic.transform = CGAffineTransform(scaleX: (1-percentOffset.x)/0.25, y: (1-percentOffset.x)/0.25)
slides[4].mainPic.transform = CGAffineTransform(scaleX: percentOffset.x, y: percentOffset.x)
}
}
Not sure about your current implementation but your solution will be something like, I'm assuming your slides array contains UIView for each slide. Change the code according to your current implementation.
(void)scrollViewDidScroll:(UIScrollView *)scrollView {
if(if(scrollView.contentOffset.x < 0)) {
CGPoint newOffset = CGPointMake(scrollView.bounds.size.width+scrollView.contentOffset.x, scrollView.contentOffset.y);
[scrollView setContentOffset:newOffset];
[self rotateViewsRight];
}
else if(scrollView.contentOffset.x > scrollView.bounds.size.width*2) {
CGPoint newOffset = CGPointMake(scrollView.contentOffset.x-scrollView.bounds.size.width, scrollView.contentOffset.y);
[scrollView setContentOffset:newOffset];
[self rotateViewsLeft];
}
}
-(void)rotateViewsRight {
UIView *endView = [slides lastObject];
[slides removeLastObject];
[slides insertObject:endView atIndex:0];
[self setContentViewFrames];
}
-(void)rotateViewsLeft {
UIView *endView = slides[0];
[slides removeObjectAtIndex:0];
[slides addObject:endView];
[self setContentViewFrames];
}
-(void) setContentViewFrames {
for(int i = 0; i < 5; i++) {
UIView * view = slides[i];
[view setFrame:CGRectMake(self.view.bounds.size.width*i, 0, self.view.bounds.size.width, self.view.bounds.size.height)];
}
}

Change Height of UIProgressbar and give border and corner radius

Currently, I am working with UIProgressView where I need to change the height of the UIProgressView. I achieve through below code.
extension UIProgressView {
#IBInspectable var barHeight : CGFloat {
get {
return transform.d * 2.0
}
set {
// 2.0 Refers to the default height of 2
let heightScale = newValue / 2.0
let c = center
transform = CGAffineTransform(scaleX: 1.0, y: heightScale)
center = c
self.layer.cornerRadius = 20.0
}
}
}
but when I set cornerRadius to UIProgressView not get affected.
You also need to tell that to not draw outside its bounds, after setting the cornerRadius.
//this is on the layer level
self.progressView.layer.masksToBounds = true
//another way is to use clipToBounds
self.progressView.clipsToBounds = true

Swift round up for respondsToSelector() and sizeWithFont()

I am translating the following Objective-C code into Swift:
- (void)layoutSubviews
{
CGSize valueLabelSize = CGSizeZero;
if ([self.valueLabel.text respondsToSelector:#selector(sizeWithAttributes:)])
{
valueLabelSize = [self.valueLabel.text sizeWithAttributes:#{NSFontAttributeName:self.valueLabel.font}];
}
else
{
valueLabelSize = [self.valueLabel.text sizeWithFont:self.valueLabel.font];
}
CGFloat xOffset = ceil((self.bounds.size.width - valueLabelSize.width) * 0.5);
self.valueLabel.frame = CGRectMake(xOffset, ceil(self.bounds.size.height * 0.5) - ceil(valueLabelSize.height * 0.5), valueLabelSize.width, valueLabelSize.height);
}
This code basically loads dynamically an UILabel in the middle of the UIView respecting the relative font size and the padding of the view. This is the code I currently have:
override func layoutSubviews()
{
var valueSize = CGSizeZero
if valueLabel.text != nil {
if ((self.valueLabel.text! as NSString).respondsToSelector(Selector("sizeWithAttributes:"))){
valueSize = (self.valueLabel.text! as NSString).sizeWithAttributes([NSFontAttributeName:self.valueLabel.font])
}
}
let xOffSet = ceil((self.bounds.size.width - valueSize.width) * 0.5)
valueLabel.frame = CGRectMake(xOffSet, ceil(self.bounds.size.height * 0.5) - ceil(valueSize.height * 0.5), valueSize.width, valueSize.height)
}
This is not working correctly cause the label appears in the left-top corner of the view. I believe there is some round up in swift for not using the respondsToSelector() method but do not know how. I also want to avoid using sizeWithFont() cause it's deprecated.
Any help will be very welcomed!
I finally made it work using the superview property:
override func layoutSubviews() {
super.layoutSubviews()
var valueSize = CGSizeZero
if let textValue = valueLabel.text {
valueSize = textValue.sizeWithAttributes([NSFontAttributeName:self.valueLabel.font])
}
var unitSize = CGSizeZero
if let textUnit = unitLabel.text {
unitSize = textUnit.sizeWithAttributes([NSFontAttributeName:self.unitLabel.font])
}
let xOffset = ceil((superview!.bounds.size.width - (valueSize.width + unitSize.width)) * 0.5)
valueLabel.frame = CGRectMake(xOffset, ceil(superview!.bounds.size.height * 0.5) - ceil(valueSize.height * 0.5), valueSize.width, valueSize.height)
unitLabel.frame = CGRectMake(CGRectGetMaxX(valueLabel.frame), ceil(superview!.bounds.size.height * 0.5) - ceil(unitSize.height * 0.5) + iMinistryValueInformationViewPadding, unitSize.width, unitSize.height)
}
Thanks anyway for the help!
Have you tried using label.sizeToFit() to set the correct label size to fit your content?
For example:
class CustomView : UIView {
lazy var textLabel: UILabel = {
let label = UILabel()
// Setup your label's text attributes here.
// e.g. font size
self.addSubview(label)
return label
}()
override func layoutSubviews() {
super.layoutSubviews()
// By this point the `text` for the label and all attributes
// that could affect the text size need to be set.
textLabel.sizeToFit()
textLabel.center = CGPoint(x: frame.width / 2, y: frame.height / 2)
}
}
The result:
Hope that helps!

Apply CGAffineTransformScale to UIView makes the layer border go bigger too

I have a UIView, inside it I have a UIImageView. I have a UIPinchGestureRecognizer added to the UIVIew to handle the pinch and zoom and make the UIView grow with the UIImageView altogether.
My UIView has a border. I added the border this way:
self.layer.borderColor = [UIColor blackColor].CGColor;
self.layer.borderWidth = 1.0f;
self.layer.cornerRadius = 8.0f;
And the problem I'm having is I can't find a way of making my UIView bigger while keeping the same width of the border. When pinching and zooming the border gets thicker.
This is my UIPinchGestureRecognizer handler:
- (void)scale:(UIPanGestureRecognizer *)sender{
if([(UIPinchGestureRecognizer*)sender state] == UIGestureRecognizerStateBegan) {
_lastScale = 1.0;
}
CGFloat scale = 1.0 - (_lastScale - [(UIPinchGestureRecognizer*)sender scale]);
CGAffineTransform currentTransform = self.transform;
CGAffineTransform newTransform = CGAffineTransformScale(currentTransform, scale, scale);
_lastScale = [(UIPinchGestureRecognizer*)sender scale];
[self setTransform:newTransform];
}
Thanks a lot!!
I've been googling around A LOT and found this:
self.layer.borderWidth = 2.0f / scaleFactor;
Sadly is not working for me... It makes sense but not working.
Also I read the solution about adding an other view in the back and making the front view to have an offset in the position so the back view is shown and looks like a border. That's not an option because I need my image to view transparent.
I want to recreate what Aviary does in their app. You can scale up and down an "sticker" and the border always stays the same size.
I was trying to do this effect in this way as well in Swift 4.2. Ultimately I could not do it successfully using CGAffine Effects. I ultimately had to update the constraints -- as I was using anchors.
Here is the code I ultimately started using. SelectedView is the view that should be scaled.
Keep in mind that this does not break other CGAffine effects. I'm still using those for rotation.
#objc private func scaleSelectedView(_ sender: UIPinchGestureRecognizer) {
guard let selectedView = selectedView else {
return
}
var heightDifference: CGFloat?
var widthDifference: CGFloat?
// increase width and height according to scale
selectedView.constraints.forEach { constraint in
guard
let view = constraint.firstItem as? UIView,
view == selectedView
else {
return
}
switch constraint.firstAttribute {
case .width:
let previousWidth = constraint.constant
constraint.constant = constraint.constant * sender.scale
widthDifference = constraint.constant - previousWidth
case .height:
let previousHeight = constraint.constant
constraint.constant = constraint.constant * sender.scale
heightDifference = constraint.constant - previousHeight
default:
return
}
}
// adjust leading and top anchors to keep view centered
selectedView.superview?.constraints.forEach { constraint in
guard
let view = constraint.firstItem as? UIView,
view == selectedView
else {
return
}
switch constraint.firstAttribute {
case .leading:
guard let widthDifference = widthDifference else {
return
}
constraint.constant = constraint.constant - widthDifference / 2
case .top:
guard let heightDifference = heightDifference else {
return
}
constraint.constant = constraint.constant - heightDifference / 2
default:
return
}
}
// reset scale after applying in order to keep scaling linear rather than exponential
sender.scale = 1.0
}
Notes: width and height anchors are on the view itself. Top and Leading anchors are on the superview -- hence the two forEach blocks.
I had to figure out a way of keeping my borders the same width during transforms (also creating stickers), and needed the solution to work with CGAffineTransform because my stickers rotate (and you cannot easily use frame-based solutions if you're doing rotations).
My approach was to change the layer.borderWidth in response to the transform changing, like this. Works for me.
class MyView : UIView {
let borderWidth = CGFloat(2)
override var transform: CGAffineTransform {
didSet {
self.setBorder()
}
}
override func viewDidLoad() {
super.viewDidLoad()
self.setBorder()
}
fileprivate func setBorder() {
self.layer.borderWidth = self.borderWidth / transform.scale
}
}
extension CGAffineTransform {
var scale: CGFloat {
return sqrt((a * a + c * c))
}
}
while setting CGAffineTransformScale, you wont be able to control the border-width separately.
The solution would be to use another view say borderView as subview in above view hierarchy than the view to be scaled (with backgroundColor as clearcolor)whose SIZE should be changed according to scale factor of other view.
Apply your border-width to the borderView keeping the desired borderwidth.
Best of luck!
Switch from CGAffineTransform to view's frame.
Sample code for pinching a view with constant border width:
#interface ViewController ()
#property (nonatomic, weak) IBOutlet UIView *testView;
#end
#implementation ViewController {
CGFloat _lastScale;
CGRect _sourceRect;
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.testView.backgroundColor = [UIColor yellowColor];
self.testView.layer.borderColor = [UIColor blackColor].CGColor;
self.testView.layer.borderWidth = 1.0f;
self.testView.layer.cornerRadius = 8.0f;
_sourceRect = self.testView.frame;
_lastScale = 1.0;
}
- (IBAction)scale:(UIPanGestureRecognizer *)sender{
if([(UIPinchGestureRecognizer*)sender state] == UIGestureRecognizerStateBegan) {
_lastScale = 1.0;
}
CGFloat scale = 1.0 - (_lastScale - [(UIPinchGestureRecognizer*)sender scale]);
CGPoint center = self.testView.center;
CGRect newBounds = CGRectMake(0, 0, _sourceRect.size.width * scale, _sourceRect.size.height * scale);
self.testView.bounds = newBounds;
self.testView.layer.transform = CATransform3DMakeRotation(M_PI * (scale - 1), 0, 0, 1);
}
#end
UPD:
I have checked Aviary app: scale by frame, rotate by CGAffineTransform. You may need to implement some additional logic to make it work.
UPD:
Use bounds and play with rotation
Without setting the transform, scale the size of view by changing it's bounds. This solution works for me.
You can add a method in the view class you want to scale, sample code:
- (void)scale:(CGFloat)scale {
self.bounds = CGRectMake(0, 0, CGRectGetWidth(self.bounds) * scale, CGRectGetHeight(self.bounds) * scale);
}

Resources