Sorry for the header, I just did not know how to call it.
I want to make my scrollView able to scroll from top or bottom until the circle in the center, like LinkedIn does it:
So there you can scroll your image until the circle in the center. How can I improve my code, to achieve this?
My code is:
scrollView!.delegate = self
imageView.frame = CGRectMake(0, 0, (scrollView?.frame.width)!, (scrollView?.frame.height)!)
if let validImage = self.avatarImage {
self.imageView.image = validImage
imageView.contentMode = .ScaleAspectFill
imageView.frame = avatarImageFrame!
}
imageView.userInteractionEnabled = true
scrollView?.addSubview(imageView)
scrollView?.contentSize = (avatarImage?.size)!
let scrollViewFrame = scrollView?.frame
let scaleWidth = (scrollViewFrame?.size.width)! / (scrollView?.contentSize.width)!
let scaleHeight = (scrollViewFrame?.size.height)! / (scrollView?.contentSize.height)!
let minScale = min(scaleHeight, scaleWidth)
scrollView?.minimumZoomScale = minScale
scrollView?.maximumZoomScale = 1
scrollView?.zoomScale = minScale
centerScrollViewContents()
}
func centerScrollViewContents() {
let boundsSize = scrollView?.bounds.size
var contentsFrame = imageView.frame
if contentsFrame.size.width < boundsSize?.width {
contentsFrame.origin.x = ((boundsSize?.width)! - contentsFrame.size.width) / 2
} else {
contentsFrame.origin.x = 0
}
if contentsFrame.size.height < boundsSize?.height {
contentsFrame.origin.y = ((boundsSize?.height)! - contentsFrame.size.height) / 2
} else {
contentsFrame.origin.y = 0
}
contentsFrame.size.height = UIScreen.mainScreen().bounds.height
imageView.frame = contentsFrame
}
func scrollViewDidZoom(scrollView: UIScrollView) {
centerScrollViewContents()
}
There are many ways to achieve this. Probably what would work best with the scroll view is to set the frame of the scroll view the same as is the frame of the circle. The content size should still be the same as the background (or rather the image in your case) and when you first enter the screen the content offset should be set so that the image is in center.
So till now you can imagine a small scroll view just behind the circle but the rest of the background is missing. To fix this all you need to do is disable bound clipping on the scroll view which is set to true by default. So set "clip subviews" to false.
Now you can already see the whole image and bouncing is correct to always keep the image inside the circle but one more thing is missing. Since the scroll view is actually quite small you can see you may not interact with it outside its frame. This is a natural behavior which can be changed by overriding a method called hitTest. This method will return a view which should collect the interaction. So we need to subclass the superview of the scroll view:
Regardless of where you do this in the interface builder or in the code I expect you have some view (will call it background) which contains a scroll view (the small one) and an overlay (the one with the circle). The background must be subclassed and have a reference to the scroll view. Then simply override the hit test method like so:
override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {
var myFrame = frame
myFrame.origin = CGPointZero // maybe this should be commented out
if CGRectContainsPoint(myFrame, point) {
return scrollView
} else {
return super.hitTest(point, withEvent: event)
}
}
By doing this the scroll view is interactable from anywhere within the background, whole image is visible on the background, the image will always be within the circle.
Good luck.
Related
I have a UIView .xib, and am subclassing UIView. When I load the Nib, the frame of the view is set and awakeFromNib() is called.
I have 3 buttons. When I load the view, I pass in callbacks for each button. If one or more of the callbacks is nil, then I hide the buttons and resize the view:
let viewSize = 50
var adjustedSize = 0
if (self.option2String == nil){
self.option2View.isHidden = true
adjustedSize -= viewSize
}
if (self.option3String == nil){
self.option3View.isHidden = true
adjustedSize -= viewSize
}
let _size = self.innerView.frame.size
let size = CGSize(width: _size.width, height: _size.height + CGFloat(adjustedSize))
self.innerView.frame = CGRect(origin: self.innerView.frame.origin, size: size)
I have tried putting this code in awakeFromNib(), and didMoveToSuperview(), but the frame does not change size.
If I enclose the last line in DispatchQueue.main.async, then it works. But I'm concerned that this is just luck due to timing.
Is this best practice? Where can I resize a view from within a UIView subclass?
EDIT: Confirmed, the DispatchQueue.main.async is just luck. It only works 50% of the time.
Having the view inside a UIViewController, call a function that resizes your view inside the controller's viewDidLayoutSubviews()
override func viewDidLayoutSubviews() {
myView.resize()
}
I have two (possibly more) views in a UIScrollView and want to use paging with it. The problem arises when I try to use the default Paging option for UIScrollView, since the views have different widths it can not page properly.
So I have implemented a custom paging code which works. However, when the scrolls are slow, it does not function as expected. (It goes back to the original position without animation.)
Here is how I currently do the custom paging through the UIScrollViewDelegate
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
if direction == 1{
targetContentOffset.pointee.x = 0
}else{
targetContentOffset.pointee.x = 100
}
}
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
if scrollView.panGestureRecognizer.translation(in: scrollView.superview).x > 0 {
direction = 1
}
else {
direction = 0
}
}
What I want:
What I have:
try to below example for Custom UIScrollView Class
import UIKit
public class BaseScrollViewController: UIViewController, UIScrollViewDelegate {
public var leftVc: UIViewController!
public var middleVc: UIViewController!
public var rightVc: UIViewController!
public var initialContentOffset = CGPoint() // scrollView initial offset
public var maximumWidthFirstView : CGFloat = 0
public var scrollView: UIScrollView!
public class func containerViewWith(_ leftVC: UIViewController,
middleVC: UIViewController,
rightVC: UIViewController) -> BaseScrollViewViewController {
let container = BaseScrollViewViewController()
container.leftVc = leftVC
container.middleVc = middleVC
container.rightVc = rightVC
return container
}
override public func viewDidLoad() {
super.viewDidLoad()
setupHorizontalScrollView()
}
func setupHorizontalScrollView() {
scrollView = UIScrollView()
scrollView.isPagingEnabled = true
scrollView.showsHorizontalScrollIndicator = false
scrollView.bounces = false
let view = (
x: self.view.bounds.origin.x,
y: self.view.bounds.origin.y,
width: self.view.bounds.width,
height: self.view.bounds.height
)
scrollView.frame = CGRect(x: view.x,
y: view.y,
width: view.width,
height: view.height
)
self.view.addSubview(scrollView)
let scrollWidth = 3 * view.width
let scrollHeight = view.height
scrollView.contentSize = CGSize(width: scrollWidth, height: scrollHeight)
leftVc.view.frame = CGRect(x: 0,
y: 0,
width: view.width,
height: view.height
)
middleVc.view.frame = CGRect(x: view.width,
y: 0,
width: view.width,
height: view.height
)
rightVc.view.frame = CGRect(x: 2 * view.width,
y: 0,
width: view.width,
height: view.height
)
addChildViewController(leftVc)
addChildViewController(middleVc)
addChildViewController(rightVc)
scrollView.addSubview(leftVc.view)
scrollView.addSubview(middleVc.view)
scrollView.addSubview(rightVc.view)
leftVc.didMove(toParentViewController: self)
middleVc.didMove(toParentViewController: self)
rightVc.didMove(toParentViewController: self)
scrollView.contentOffset.x = middleVc.view.frame.origin.x
scrollView.delegate = self
}
public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
self.initialContentOffset = scrollView.contentOffset
}
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
if maximumWidthFirstView != 0
{
if scrollView.contentOffset.x < maximumWidthFirstView
{
scrollView.isScrollEnabled = false
let newOffset = CGPoint(x: maximumWidthFirstView, y: self.initialContentOffset.y)
self.scrollView!.setContentOffset(newOffset, animated: false)
scrollView.isScrollEnabled = true
}
}
}
}
Use of BaseScrollViewController
let left = FirstController.init()
let middle = MiddleController()
let right = RightController.init()
let container = BaseScrollViewController.containerViewWith(left,middleVC: middle,rightVC: right)
container.maximumWidthFirstView = 150
Output:
GitHub gist Example code: https://gist.github.com/mspvirajpatel/58dac2fae0d3b4077a0cb6122def6570
I have previously written a short memo about this problem, and I'll copy/paste it since it is no longer accessible from anywhere. This may not be a specific answer and the codes are pretty old, but I hope this would help you in some degree.
If you have used a paging feature included in UIScrollView, you might also have tempted to customize the width of each page instead of a default, boring, frame width paging. It would be great if you can make the scroll stop at shorter or longer intervals than just multiples of its frame width. Surprisingly, there's no built-in way to configure the width of pages even in our latest iOS7 SDK. There are some ways to achieve custom paging, but none of them I would say are complete. As for now, you'll have to choose either of the following solutions.
1. Change the frame size of your UIScrollView
Alexander Repty has introduced a nice and easy solution to this problem and also included a sample code through his blog: http://blog.proculo.de/archives/180-Paging-enabled-UIScrollView-With-Previews.html
Basically, the instruction can be watered down to the following steps:
Create UIView subclass and override hitTest: withEvent:.
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
if ([self pointInside:point withEvent:event]) {
if ([self.subviews count] == 0) return nil;
else return [self.subviews lastObject];
}
return nil;
}
Include UIScrollView as a subview of the above UIView subclass.
Adjust the frame size of your UIScrollView.
Set clipsToBound property of your scroll view to NO.
Set pagingEnabled property of your scroll view to YES.
As you can see, I've just assumed that there is only one subview (the scrollView!) to your UIView subclass. Since you are passing all the touch events occurred in the UIView subclass to your UIScrollView, you'll be able to scroll the content by panning on the UIView subclass, but the paging width will be decided by the width of UIScrollView's frame.
The best part of this approach is that you'll get the genuine feeling and responsiveness, as it is somewhat hard to mimic the paging by using UIScrollView delegate methods.
The only problem I found using this solution is that the width of all pages will have to be identical. You can't set different widths to different pages. If you tries to change your scrollView's frame size dynamically, you'll find there're a number of new emerging problems to deal with. Before trying to fix these glitches, you may want to check out other two solutions using UIScrollView delegates.
2. scrollViewWillEndDragging: withVelocity: targetContentOffset
scrollViewWillEndDragging: withVelocity: targetContentOffset is one of the latest UIScrollView delegate methods(iOS 5.0 or up) that gives you more information than the other old ones.
Since you get the velocity of the scrollView right after you lift the finger up from the screen, we can figure out the direction of the scrolled contents. The last argument, targetContentOffset, not only gives you the expected offset when the scrolling stops eventually, you can also assign CGPoint value in order to let the scrollView scrolls to the desired point.
targetContentOffset = CGPointMake(500, 0);
or
targetContentOffset->x = 500;
However, this will not work as you would think it should because you cannot set the speed of scrolling animation. It feels more like the scrollView happens to stop at the right point rather than it snaps to the spot. I also have to warn you that manually scrolling the contents with setContentOffset: animated: or just by using UIView animation inside the method will not work as expected.
If the velocity is 0, however, you may(and you have to) use manual scrolling to make it snap to the nearest paging point.
It could be the simplest and the most clean approach among all, but the major downside is that it does not provide the same experience that you always had with the real paging feature. To be more honest, it's not even similar to what we call paging. For the better result, we need to combine more delegate methods.
3. Use multiple UIScrollView delegate methods
From my shallow experience, an attempt to scroll your scrollView manually inside any UIScrollView delegate methods will only work when your scrollView has started to decelerate, or when it's not scrolling at all. Therefore, the best place I've found to perform the manual scrolling is scrollViewWillBeginDecelerating:.
Before looking inside the sample code, remember scrollViewEndDragging: withVelocity: targetContentOffset: method will always called prior to scrollViewWillBeginDecelerating:.
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
{
_scrollVelocity = velocity.x;
if (_scrollVelocity == 0) {
// Find the nearest paging point and scroll.
}
}
- (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView
{
if (_scrollVelocity < 0) {
[UIView animateWithDuration:0.3 delay:0.0 options:UIViewAnimationOptionCurveEaseOut animations:^{
scrollView.contentOffset = // Previous page offset
} completion:^(BOOL finished){}];
} else if (_scrollVelocity > 0) {
// Animate to the next page offset
}
}
_scrollVelocity is meant to be a global variable or a property, and I've assumed that you have your own ways to decide paging offsets for each page. Note that you'll have to handle the case of zero velocity inside the upper method because the latter method will not be called.
UIView animation with the duration 0.3 and the EaseOut curve option gave me the best result, but of course you should try other combinations to find what's the best for you.
This not the exact solution you might be looking for.
1) Check the offset of the scrollView when it reaches 0, You could show the VIEW you have above , You could animate while checking the scrollview movement so that it looks nice .But not completely
2) Now the VIEW is partially above your camera(you can decrease it alpha so that scrollview is still visible).
3) user can tap the view and you can show it completely.
You may want to consider calculating the most visible cell in your collection view after dragging ends and then programmatically scroll to – and center – that cell.
So something like:
First, implement the scrollViewDidEndDragging(_:willDecelerate:) method of your collection view's delegate. Then, in that method's body, determine which cell in collectionView.visibleCells is most visible by comparing each of their centers against your collection view's center. Once you find your collection view's most visible cell, scroll to it by calling scrollToItem(at:at:animated:).
First of all I have checked almost every places over the internet but I didn't get any solution about this topic.
In my cases I have multiple UIView objects inside a superview or you can say a canvas where I am drawing this views.
All this views are attached with pan gesture so they can be moved inside anywhere of their superview.
Some of this views can be rotated using either rotation gesture or CGAffineTransformRotate.
Whenever any of the view will be outside of the main view then it will be deleted.
Now following are my code.
#IBOutlet weak var mainView: UIView!
var newViewToAdd = UIView()
newViewToAdd.layer.masksToBounds = true
var transForm = CGAffineTransformIdentity
transForm = CGAffineTransformScale(transForm, 0.8, 1)
transForm = CGAffineTransformRotate(transForm, CGFloat(M_PI_4)) //Making the transformation
newViewToAdd.layer.shouldRasterize = true //Making the view edges smooth after applying transforamtion
newViewToAdd.transform = transForm
self.mainView.addSubview(newViewToAdd) //Adding the view to the main view.
Now in case the gesture recognizer its inside the custom UIView Class -
var lastLocation: CGPoint = CGPointMake(0, 0)
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
self.superview?.bringSubviewToFront(self)
lastLocation = self.center //Getting the last center point of the view on first touch.
}
func detectPan(recognizer: UIPanGestureRecognizer){
let translation = recognizer.translationInView(self.superview!) //Making the translation
self.center = CGPointMake(lastLocation.x + translation.x, lastLocation.y + translation.y) //Updating the center point.
switch(recognizer.state){
case .Began:
break
case .Changed:
//MARK: - Checking The view is outside of the Superview or not
if (!CGRectEqualToRect(CGRectIntersection(self.superview!.bounds, self.frame), self.frame)) //if its true then view background color will be changed else background will be replaced.
{
self.backgroundColor = outsideTheViewColor
var imageViewBin : UIImageView
imageViewBin = UIImageView(frame:CGRectMake(0, 0, 20, 25));
imageViewBin.image = UIImage(named:"GarbageBin")
imageViewBin.center = CGPointMake(self.frame.width/2, self.frame.height/2)
addSubview(imageViewBin)
}else{
for subViews in self.subviews{
if subViews.isKindOfClass(UIImageView){
subViews.removeFromSuperview()
}
self.backgroundColor = deSelectedColorForTable
}
}
case .Ended:
if (!CGRectEqualToRect(CGRectIntersection(self.superview!.bounds, self.frame), self.frame)) //If its true then the view will be deleted.
{
self.removeFromSuperview()
}
default: break
}
}
The main problem is if the view is not rotated or transformed then all the "CGRectIntersection" inside the .Changed/.Ended case is working fine as expected but if the view is rotated or transformed then "CGRectIntersection" always becoming true even the view is inside the "mainView" and its removing from the mainview/superview.
Please help about my mistake.
Thanks in advance.
Frame of the view gets updated after applying transform. Following code ensures that it is inside the its superviews bounds.
if (CGRectContainsRect(self.superview!.bounds, self.frame))
{
//view is inside of the Superview
}
else
{
//view is outside of the Superview
}
Background
In order to make a text view that scrolls horizontally for vertical Mongolian script, I made a custom UIView subclass. The class takes a UITextView, puts it in a UIView, rotates and flips that view, and then puts that view in a parent UIView.
The purpose for the rotation and flipping is so that the text will be vertical and so that line wrapping will work right. The purpose of sticking everything in a parent UIView is so that Auto layout will work in a storyboard. (See more details here.)
Code
I got a working solution. The full code on github is here, but I created a new project and stripped out all the unnecessary code that I could in order to isolate the problem. The following code still performs the basic function described above but also still has the slow loading problem described below.
import UIKit
#IBDesignable class UIMongolTextView: UIView {
private var view = UITextView()
private var oldWidth: CGFloat = 0
private var oldHeight: CGFloat = 0
#IBInspectable var text: String {
get {
return view.text
}
set {
view.text = newValue
}
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override init(frame: CGRect){
super.init(frame: frame)
}
override func sizeThatFits(size: CGSize) -> CGSize {
// swap the length and width coming in and going out
let fitSize = view.sizeThatFits(CGSize(width: size.height, height: size.width))
return CGSize(width: fitSize.height, height: fitSize.width)
}
override func layoutSubviews() {
super.layoutSubviews()
// layoutSubviews gets called multiple times, only need it once
if self.frame.height == oldHeight && self.frame.width == oldWidth {
return
} else {
oldWidth = self.frame.width
oldHeight = self.frame.height
}
// Remove the old rotation view
if self.subviews.count > 0 {
self.subviews[0].removeFromSuperview()
}
// setup rotationView container
let rotationView = UIView()
rotationView.frame = CGRect(origin: CGPointZero, size: CGSize(width: self.bounds.height, height: self.bounds.width))
rotationView.userInteractionEnabled = true
self.addSubview(rotationView)
// transform rotationView (so that it covers the same frame as self)
rotationView.transform = translateRotateFlip()
// add view
view.frame = rotationView.bounds
rotationView.addSubview(view)
}
func translateRotateFlip() -> CGAffineTransform {
var transform = CGAffineTransformIdentity
// translate to new center
transform = CGAffineTransformTranslate(transform, (self.bounds.width / 2)-(self.bounds.height / 2), (self.bounds.height / 2)-(self.bounds.width / 2))
// rotate counterclockwise around center
transform = CGAffineTransformRotate(transform, CGFloat(-M_PI_2))
// flip vertically
transform = CGAffineTransformScale(transform, -1, 1)
return transform
}
}
Problem
I noticed that the custom view loads very slowly. I'm new to Xcode Instruments so I watched the helpful videos Debugging Memory Issues with Xcode and Profiler and Time Profiler.
After that I tried finding the issue in my own project. It seems like no matter whether I use the Time Profiler or Leaks or Allocations tools, they all show that my class init method is doing too much work. (But I kind of knew that already from the slow load time before.) Here is a screen shot from the Allocations tool:
I didn't expand all of the call tree because it wouldn't have fit. Why are so many object being created? When I made a three layer custom view I knew that it wasn't ideal, but the number of layers that appears to be happening from the call tree is ridiculous. What am I doing wrong?
You shouldn't add or delete any subview inside layoutSubviews, as doing so triggers a call to layoutSubviews again.
Create your subview when you create your view, and then only adjust its position in layoutSubviews rather than deleting and re-adding it.
I am trying to create a growing UITableViewHeader on UITableView. I have a UITableView and a mapView set in the tableHeaderView of UITableView.
tblView.bounces = true
tblView.bouncesZoom = true
tblView.alwaysBounceVertical = true
mapView.frame = CGRectMake(0, 0, self.view.frame.size.width, CGFloat(kMapHeaderHeight))
mapView.mapType = MKMapType.Standard
mapView.zoomEnabled=true
mapView.scrollEnabled = true
mapView.delegate = mapHelper
tblView.tableHeaderView = mapView
And also implemented scrollViewDidScroll delegate and whenever it scrolls down, I have changed the frame of headerview as
func scrollViewDidScroll(scrollView: UIScrollView) {
var scrollOffset = scrollView.contentOffset.y
println("\(scrollOffset)")
var headerFrame : CGRect = self.mapView.frame
if (scrollOffset < 0){
headerFrame.size.height -= scrollView.contentOffset.y/3
}
self.mapView.frame = headerFrame
}
However, it does not grow as expected without bouncing.Seems very unclear. Any help?
I am following these tutorials to create a Growing UITableViewheader when pulling down as
UITableVIew header without bouncing when pull down ,
Expand UITableView Header View to Bounce Area When Pulling Down
Here is the link of the project :
https://drive.google.com/file/d/0B6dTvD1JbkgBVENUS1ROMzI0Wnc/vie
EDITED: i somehow managed to have the effect but the animation seems very slow
func scrollViewDidScroll(scrollView: UIScrollView) {
let yPos: CGFloat = -scrollView.contentOffset.y
if (yPos > 0) {
var mapViewRect: CGRect = self.mapView.frame
mapViewRect.origin.y = scrollView.contentOffset.y
mapViewRect.size.height = kHeaderHeight+yPos
self.mapView.frame = mapViewRect
}
}
let kHeaderHeight:CGFloat = 200
I suggest you to use this tutorial.
The most important parts of it:
Create a scrollView (or whatever that is a subclass of a UIScrollView) and a separate view (which will be functioning as a headerView, so let's call if headerView)
add the headerView and the scrollView as a subView of your view
implement the scrollViewDidScroll method, and put the framing logic there (of course, if you're using autolayout, you have to manage constraints there)
Actually the animation was not working well in simulator of xcode6.3. I tried 2 days for this and posted a bounty here but when i finally I tested it on real device and found the MapView was properly bouncing.If anyone needs it..here is the piece of logic.
let kHeaderHeight:CGFloat = 380
class NewBookingVC: UIViewController {
#IBOutlet weak var tblView: UITableView!
let mapView : MKMapView = MKMapView()
var customTableHeaderView:UIView = UIView()
override func viewDidLoad() {
super.viewDidLoad()
tblView.delegate = self
tblView.dataSource = self
mapView.frame = CGRectMake(0, 0, self.view.frame.size.width, 380)
mapView.mapType = MKMapType.Standard
mapView.zoomEnabled=true
mapView.scrollEnabled = true
customTableHeaderView = UIView(frame: CGRectMake(0, 0, self.view.frame.size.width, 380))
customTableHeaderView.addSubview(mapView)
tblView.tableHeaderView = customTableHeaderView
}
func scrollViewDidScroll(scrollView: UIScrollView) {
let yPos: CGFloat = -scrollView.contentOffset.y
if (yPos > 0) {
var mapViewRect: CGRect = self.mapView.frame
mapViewRect.origin.y = scrollView.contentOffset.y
mapViewRect.size.height = kHeaderHeight+yPos
self.mapView.frame = mapViewRect
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Actually what you have to do is implement the scrollview delegate for table view because it inherits from scrollview and you can implement scrollViewDidScroll delegate and whenever it scrolls down, change the frame of headerview.
Idea behind this while scrolling you always have to keep the y-position of the MKMapView at the zero position....and height should incerase accordingly...
func scrollViewDidScroll(scrollView: UIScrollView) {
let yPos: CGFloat = -scrollView.contentOffset.y
if (yPos > 0) {
//adjust your mapview y-pos and height
//adjusting new position is all about real math
}
}
Performance in the iOS Simulator is not expected to match performance on device. The iOS Simulator is meant as a tool for rapid prototyping and fast iteration.In your case too, redrawing of MapView performance is quite slow in the simulator because at each Scroll you are calculating its new frame and height. So it takes some time adjusting new frame and seems very slow.
However it works well while tuning on real devices.