Swift 2 - UIScrollView print out zoomScale - uiscrollview

In my app I use a UIScrollView with the option to zoom it.
My code works but what I am trying to achieve now is to get the current zooming value while zooming the view.
Using the code below I get my app to crash with error: `(llbd)'. Is there a way I can achieve this?
func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView? {
let currentScale = scrollView.zoomScale // error: (llbd)
print(currentScale)
if currentScale == 1.0 {
print("zoom scale is = 1")
}else if currentScale > 1 {
print("zoom scale is > 1")
}
return scrollView.viewWithTag(VIEW_FOR_ZOOM_TAG)
}

Solved using:
func scrollViewDidEndZooming(scrollView: UIScrollView, withView view: UIView?, atScale scale: CGFloat) {
print(scale)
}

Related

AnimatableData for Line Chart in SwiftUI

I'd like to create animation of the line chart like drawing from start point to end point.
I've created InsettableShape (code below) in order to use with strokeBorder(), but can't figure out how to define animatableData.
Thank you!
(I know I can do it with Shape and trim(), but I need inset.)
import SwiftUI
struct LineChartInsettableShape: InsettableShape {
let series: [CGFloat]
var insetAmount: CGFloat = 0
func inset(by amount: CGFloat) -> some InsettableShape {
var chart = self
chart.insetAmount += amount
return chart
}
func path(in rect: CGRect) -> Path {
guard !series.isEmpty else { return Path() }
let minY = series.min()!
let maxY = series.max()!
let xStep = (rect.width - insetAmount * 2) / CGFloat(series.count - 1)
func point(index: Int) -> CGPoint {
let yValue: CGFloat = CGFloat(series[index])
return CGPoint(
x: xStep * CGFloat(index) + insetAmount,
y: (rect.height - insetAmount * 2) * (1 - (yValue - minY) / (maxY - minY)) + insetAmount
)
}
return Path { path in
path.move(to: point(index: 0))
for index in 1..<series.count {
path.addLine(to: point(index: index))
}
}
}
}
It's not hard once you figure it out, but initially how this works can be a bit confusing. You need to have a property named animatableData which tells SwiftUI the name of the variable you'll be using for animation, and how to get it and set its value. For example, I have a small piece of animation code that uses a variable named inset, which is very similar to your insetAmount. Here's all the code my app needs to use this variable for animation:
var inset: CGFloat
var animatableData: CGFloat {
get { inset }
set { self.inset = newValue}
}
Not all data types can be used for animation. CGFloat, used here, certainly can be. Paul Hudson has an excellent short video on this topic: https://www.hackingwithswift.com/books/ios-swiftui/animating-simple-shapes-with-animatabledata.
There is a bar chart library, you can check it out as well https://github.com/dawigr/BarChart

iOS Autolayout: Oddly expand-animation of UITextView inside an UIScrollView

I'm trying to animate the height constraint of a UITextView inside a UIScrollView. When the user taps the "toggle" button, the text should appear in animation from top to bottom. But somehow UIKit fades in the complete view.
To ensure the "dynamic" height depending on the intrinsic content-size, I deactivate the height constraint set to zero.
#IBAction func toggle() {
layoutIfNeeded()
UIView.animate(withDuration: 0.6, animations: { [weak self] in
guard let self = self else {
return
}
if self.expanded {
NSLayoutConstraint.activate([self.height].compactMap { $0 })
} else {
NSLayoutConstraint.deactivate([self.height].compactMap { $0 })
}
self.layoutIfNeeded()
})
expanded.toggle()
}
The full code of this example is available on my GitHub Repo: ScrollAnimationExample
Reviewing your GitHub repo...
The issue is due to the view that is animated. You want to run .animate() on the "top-most" view in the hierarchy.
To do this, you can either create a new property of your ExpandableView, such as:
var topMostView: UIView?
and then set that property from your view controller, or...
To keep your class encapsulated, let it find the top-most view. Replace your toggle() func with:
#IBAction func toggle() {
// we need to run .animate() on the "top" superview
// make sure we have a superview
guard self.superview != nil else {
return
}
// find the top-most superview
var mv: UIView = self
while let s = mv.superview {
mv = s
}
// UITextView has subviews, one of which is a _UITextContainerView,
// which also has a _UITextCanvasView subview.
// If scrolling is disabled, and the TextView's height is animated to Zero,
// the CanvasView's height is instantly set to Zero -- so it disappears instead of animating.
// So, when the view is "expanded" we need to first enable scrolling,
// and then animate the height (to Zero)
// When the view is NOT expanded, we first disable scrolling
// and then animate the height (to its intrinsic content height)
if expanded {
textView.isScrollEnabled = true
NSLayoutConstraint.activate([height].compactMap { $0 })
} else {
textView.isScrollEnabled = false
NSLayoutConstraint.deactivate([height].compactMap { $0 })
}
UIView.animate(withDuration: 0.6, animations: {
mv.layoutIfNeeded()
})
expanded.toggle()
}

Reset offset, onTapGesture works, but onRotated does not work, why?

I want the view containing 2 rectangles floating in the bottom of the screen, whatever the orientation is portrait or landscape.
code is a test, when orientationDidChangeNotification happened, I Found UIDevice.current.orientation.isPortrait and UIScreen.main.bounds.height often have wrong value, why?
Anyway, test code is just reset offset = 0 in onRotated(). but it doesn't work; otherwise onTapGesture works fine.
Q1: Is it a wrong way for SwiftUI? SceneDelegate.orientationDidChangeNotification -> contentView.onRotated()?
Q2: why do UIDevice.current.orientation.isPortrait and UIScreen.main.bounds.height often have wrong value?
Q3: How to let a view float at the bottom of screen in both portrait and landscape?
let height: CGFloat = 100
struct TestView: View {
#State var offset = (UIScreen.main.bounds.height - height) / 2
var body: some View {
ZStack {
Text("+")
VStack(spacing: 0) {
Rectangle().fill(Color.blue)
Rectangle().fill(Color.red)
}
.frame(width: 100, height: height)
.offset(y: offset)
.onTapGesture {
self.offset = 0
}
}
}
func onRotated() {
// let isPortrait = UIDevice.current.orientation.isPortrait
offset = 0//(UIScreen.main.bounds.height - height) / 2
// print("\(isPortrait), screen height = \(UIScreen.main.bounds.height)")
}
}
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
let contentView = TestView()
if let windowScene = scene as? UIWindowScene {
NotificationCenter.default.addObserver(forName: UIDevice.orientationDidChangeNotification, object: nil, queue: nil) { notification in
contentView.onRotated()
}
let window = UIWindow(windowScene: windowScene)
window.rootViewController = UIHostingController(rootView: contentView)
self.window = window
window.makeKeyAndVisible()
}
}
...
}
contentView is not a reference, it is a value, so you call .onRotated on own copy of contentView value that lives only within callback
let contentView = TestView()
if let windowScene = scene as? UIWindowScene {
NotificationCenter.default.addObserver(forName: UIDevice.orientationDidChangeNotification, object: nil, queue: nil) { notification in
contentView.onRotated() // local copy!!!
}
instead create listener for notification publisher inside TestView, so it can change self internally.
Moreover, it is not clear the intention but SwiftUI gives possibility to track size classes via EnvironmentValues.horizontalSizeClass and EnvironmentValues.verticalSizeClass which are automatically changed on device orientation, so it is possible to make your view layout depending on those environment values even w/o notification.
See here good example on how to use size classes

UIView height constraint animation, which has anchor connection with other views

Xcode swift 4
I have some views that are added dynamically in code one below another.
Each new view top anchor is connected to previous view bottom anchor.
And each view have a button that make view to expand/collapse with animation. Here is button code :
let fullHeight : CGFloat = 240
let smallHeight : CGFloat = 44
let currentHeigth = rootView.frame.size.height //I use this to get current height and understand expanded view or not
let heighCons = rootView.constraints.filter //I use this to deactivate current height Anchor constraint
{
$0.firstAttribute == NSLayoutAttribute.height
}
NSLayoutConstraint.deactivate(heighCons)
rootView.layoutIfNeeded()
if currentHeigth == smallHeight
{
rootView.heightAnchor.constraint(equalToConstant: fullHeight).isActive = true
rootView.setNeedsLayout()
UIView.animate(withDuration: 0.5)
{
rootView.layoutIfNeeded() //animation itself
}
}
else
{
rootView.heightAnchor.constraint(equalToConstant: smallHeight).isActive = true
rootView.setNeedsLayout()
UIView.animate(withDuration: 0.5)
{
rootView.layoutIfNeeded() //animation itself
}
}
This all works perfectly but i have a problem : view that below current expanding view changes it y position immediately with no animation. Its just jumping to previous view bottom anchor, that would be active after animation finish.
So my question is :
1) what is the right way to make height constraint animation, when views are connected to each other by bottom/top animation?
2) my goal is just to make a view that would expand/collapse on button click, maybe i should do it another way?
Here's an approach using Visiblity Gone Extension
extension UIView {
func visiblity(gone: Bool, dimension: CGFloat = 0.0, attribute: NSLayoutAttribute = .height) -> Void {
if let constraint = (self.constraints.filter{$0.firstAttribute == attribute}.first) {
constraint.constant = gone ? 0.0 : dimension
self.layoutIfNeeded()
self.isHidden = gone
}
}
}
Usage
expanview.visiblity(gone: true,dimension: 0)
Example
#IBOutlet weak var msgLabel: UILabel!
#IBOutlet weak var expanview: UIView!
#IBAction func toggleCollapisbleView(_ sender: UIButton) {
if sender.isSelected{
sender.isSelected = false
expanview.visiblity(gone: false,dimension: 128)
sender.setTitle("Collapse",for: .normal)
}
else{
sender.isSelected = true
expanview.visiblity(gone: true,dimension: 0)
sender.setTitle("Expand",for: .normal)
msgLabel.text = "Visiblity gone"
}
}

UITableView cell height animation

I do widget. I need to changed cell height with animation. When click "show more" go extra controls.
enter image description here
I wrote this code
tableView.beginUpdates()
showModes(alpha: 1, duration: 0.2, delay: 0.1)
tableView.endUpdates()
func showModes(alpha: CGFloat, duration: CGFloat, delay: CGFloat) {
if tableView.numberOfRows(inSection: 0) != 0 {
for i in 0...tableView.numberOfRows(inSection: 0)-1 {
let indexPath = IndexPath(row: i, section: 0)
if let cell = tableView.cellForRow(at: indexPath) as? WidgetCell {
UIView.animate(withDuration: TimeInterval(duration), delay: TimeInterval(delay) ,animations: {
if alpha == 0 {
cell.favoriteConstraint.constant = -45
} else {
cell.favoriteConstraint.constant = 10
}
cell.favoriteMode.alpha = alpha
self.tableView.layoutIfNeeded()
})
}
}
}
}
But it does not work smoothly. it dramatically twitches up and down. How do I get this to work smoothly, for example, in the weather widget by Apple?
I can't recognise what is a problem definetly because you posted only part of your code. But I can suggest several checks to do
Did you set self-sizing cells for UITableView? AtableView.rowHeight = UITableViewAutomaticDimension;
Did you added constraint (favoriteConstraint) to cell's contentView
Also when animating constraint changes you don't need to change it inside animation closure.
The standard code looks like
cell.favoriteConstraint.constant = newValue
UIView.animateWithDuration(0.3) {
cell.contentView.layoutIfNeeded()
}
Also you don't need to wrap your method call. This must be ok:
showModes(alpha: 1, duration: 0.2, delay: 0.1)
tableView.beginUpdates()
tableView.endUpdates()

Resources