I have a UIView on top of another UIView. In that top view I have a UIImageView and on top of that there is a scrollVIew. When scrolling it used to stretch the imageView frame calculating the scroll offset. Now updating to Xcode 11.0 (11A420a) this feature is not working even though nothing has changed. Any suggestion why ? Here is my imageView constraints:
and my scrollViewDidScroll function:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let y = 135 - (self.scrollView.contentOffset.y + 135)
let h = max(120, 160 + y)
let rect = CGRect(x: 0, y: 0, width: view.bounds.width, height: h)
NHBannerImageView.frame = rect
}
As you can see there is a rect variable which value changes depending on scroll but after that image frame is not changing.
I have downloaded the Xcode 10.3 and its working straight away, with the same configuration.
Update your view layout might be solve this problem. Add -
self.view.layoutIfNeeded()
in scrollViewDidScroll
Related
i got a UICollectionView with (3*3 Grid), so when i scroll up and down, is there a way i can detect if the collection view cell is displaying more than 50% of its height on the screen?
let visibleRect = CGRect(origin: collectionView.contentOffset, size: collectionView.bounds.size)
let visiblePoint = CGPoint(x: visibleRect.minX, y: visibleRect.midY)
let visibleIndexPath = collectionView.indexPathForItem(at: visiblePoint)
I have tried this but it doesn't seems to work. Does anyone has solution for this?
You can ask for
#property(nonatomic) CGPoint contentOffset;
of UIScrollView because UICollectionView's inherits from them.
and don't forget your Cell's have a CALayer that knows the visible Rectangle as well.
CGRect visiblerect = cell.layer.visibleRect;
if (visiblerect.size.height > (cell.frame.size.height * 0.5)) {
// do stuff when cell is more visible then half its size
}
Update
After adding convert method between rect and main View, the Y position is ok, but X coordinate is shifted to the right outside of the main view:
Dropdown view(subview) is off main view
Below is button frame before and after convert method. Main view is 414 x 896. Dropdown menu somehow shifts to the right as on attached image.
button frame in stackView btnRect: (120.66666666666666, 0.0, 293.3333333333333, 30.0)
button frame in main view: cvtRect (241.33333333333331, 190.0, 293.3333333333333, 30.0)
view: Optional(>)
Goal. I want to make a dropdown list by showing UIView with dropdown options below a button. Button is inside of Vertical Stack View. I add a TableView with dropdown options to this dropdown UIView.I want to click a button and have this UIView with TableView inside to show just below the button. Basically following this tutorial https://www.youtube.com/watch?v=D3DCPaEE4hQ with the exception that my button is inside of Vertical Stack View.
Issue. UIView and TableView inside UIView show up ok when button is clicked. The issue is dropdown UIView's location that is always the same origin X=120, Y=0.
This is how I try to do it:
I have Vertical Stack with 4 rows
In 4th row I have label(width=120 almost same as X coordinate above) and a button that triggers UIView to show
I am using button to show dropdown list(basically UIView) that should appear just below button when the button is tapped, but it always appears at origin x=120 Y=0 , basically pinned to top of the right column in Vertical Stack View. Vertical Stack View has 1st column with labels, and second column with different controls like buttons etc.
func addTransparentView(frames: CGRect)
{
let window = UIApplication.shared.windows.filter {$0.isKeyWindow}.first
transparentView.frame = window?.frame ?? self.view.frame
//some of the stuff I tried to at least centre dropdownUIView
//transparentView.center.x = window?.center.x ?? self.view.center.x
//transparentView.center.y = window?.center.y ?? self.view.center.y
self.view.addSubview(transparentView)
tvPriority.frame = CGRect(x: frames.origin.x, y: frames.origin.y + frames.width, width: frames.width, height: 0)
//some of the stuff I tried to at least centre UIView
//tvPriority.center = verticalStackView.convert(verticalStackView.center, from:tvPriority)
//tvPriority.center.x = view.center.x
//tvPriority.center.y = view.center.y
self.view.addSubview(tvPriority)
tvPriority.layer.cornerRadius = 5
transparentView.backgroundColor = UIColor.black.withAlphaComponent(0.9)
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(removeTransparentView))
transparentView.addGestureRecognizer(tapGesture)
transparentView.alpha = 0
UIView.animate(withDuration: 0.4, delay: 0, usingSpringWithDamping: 1.0, initialSpringVelocity: 1.0, options: UIView.AnimationOptions.curveEaseInOut, animations: {self.transparentView.alpha = 0.5
self.tvPriority.frame = CGRect(x: frames.origin.x, y: frames.origin.y + frames.height, width: frames.width, height: 193)
}, completion: nil)
}
To successfully make dropdown list I need UIView to show up just below buttons frame(X, Y, width, height). But although button is in the 4th row which should be position with much higher Y value, buttons frame is always at X=120, Y=0, so my UIView is always pinned to this location way above button that is supposed to simulate dropdown.
Questions
1. What am I missing with positioning of the dropdown UIView? Why is buttons position Y=0 when the button is in 4th row of Vertical Stack View, with obviously much higher Y position? I also tried to simply centre this dropdown in the centre of screen but that also does not work.
2. I transitioned to iOS development from the world of web development, and I used dropdown a lot in my career. Should I just use Picker View instead? Or alert? What is the most common and most standard way of offering list of mutually exclusive options to user in Swift app?
Thanks a lot
Your button is a subview of the stackView, so its frame is relative to the frame of the stackView.
To get its frame (rect) in the view's coordinate space, you'll want to use .convert. Assign this action to one of your buttons in the stackView:
EDIT Fixed the code example... I had not checked it before posting.
class ConvertViewController: UIViewController {
#IBOutlet var dropDownView: UIView!
#IBAction func didTap(_ sender: Any) {
guard let btn = sender as? UIButton else {
fatalError("Sender is not a button!")
}
guard let sv = btn.superview as? UIStackView else {
fatalError("Sender is not in a stackView!")
}
let btnRect = btn.frame
let cvtRect = sv.convert(btn.frame, to: view)
print("button frame in stackView:", btnRect)
print("button frame in main view:", cvtRect)
let dropDownRect = dropDownView.bounds
let cvtCenterX = cvtRect.origin.x + (cvtRect.size.width / 2.0)
let viewX = cvtCenterX - (dropDownRect.width / 2.0)
let newOrigin = CGPoint(x: viewX, y: cvtRect.minY + cvtRect.height)
dropDownView.frame.origin = newOrigin
}
}
If you look at the output in the debug console, you should see something like this:
button frame in stackView: (0.0, 114.0, 46.0, 30.0)
button frame in main view: (164.5, 489.5, 46.0, 30.0)
As you can see, the rect (frame) of my 4th button in my stackView has an origin of 0.0, 114.0, but after converting it to my view coordinate space, the rect's origin is 164.5, 489.5.
You can now position your "dropdown list" relative to the converted rect.
As a side note, you may want to look at UIPopoverPresentationController.
In my swift app I've a collection view and I want to creare a stretchable header view like this in table view: https://medium.com/if-let-swift-programming/how-to-create-a-stretchable-tableviewheader-in-ios-ee9ed049aba3
You already answered your question yourself with that article link, unless I miss something.
I will copy & paste for you and others that may have the same question, if it helps, because it even ships with a github link (kudos to Abhimuralidharan # Medium):
Create a tableview with the basic datasource and delegate methods which are required to simply load the table with some data.
Set the tableview’s contentInset property:
tableView.contentInset = UIEdgeInsetsMake(300, 0, 0, 0)
Here, I set the top value as 300 which is a calculated number which I will set as the initial normal height for the header imageview. Now, that we set the contentInset , the tableview’s frame will start at (0,0) and the first cell will start at (0,300).
Now, create an imageview with height 300 and add it to the current View above the tableview.
imageView.frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width, height: 300)
imageView.image = UIImage.init(named: “poster”)
imageView.contentMode = .scaleAspectFill
imageView.clipsToBounds = true
view.addSubview(imageView)
Then, add the following code in the scrollview delegate method scrollViewDidScroll which gets called every time the tableview is scrolled.
func scrollViewDidScroll (_ scrollView: UIScrollView) {
let y = 300 — (scrollView.contentOffset.y + 300)
let height = min(max(y, 60), 400)
imageView.frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width, height: height)
}
Compile and run the code. Full source code is available in github.
I am rotating UIView by using UISLider Values. If slider values either get increase or decrease, according to that rotation happen. My code is working fine.
Rotation Action:
#IBAction func sliderToolAction(sender: UISlider) {
if sender.tag == 1
{
let radians = CGFloat(sender.value) * 22 / (7 * 180)
rotatingView.transform = CGAffineTransformMakeRotation(radians)
}
}
Output:
Rotating Frame: (100, 20, 103.9990, 127.8889)
After rotating that UIView, I tried to increase width and height of that view, its not working.
Resize Action
#IBAction func sliderToolAction(sender: UISlider) {
if sender.tag == 2
{
rotatingView.frame.size.width = CGFloat(sender.value) * viewWidth
rotatingView.frame.size.height = CGFloat(sender.value) * viewHeight
}
}
Output:
Rotating Frame: (80, 40, 207.8888, 255.6667) //ATTEMPT 1
Rotating Frame: (50, 60, 207.8888, 255.6667) //ATTEMPT 2
Rotating Frame: (20, 90, 207.8888, 255.6667) //ATTEMPT 3
Rotating Frame: (-10, 120, 207.8888, 255.6667) //ATTEMPT 4
I am increasing width and height alone, but UIView position is moving out of screen. Kindly guide me on this.
Straight from Apple's documentation:
Warning
If the transform property is not the identity transform, the value of
this property is undefined and therefore should be ignored.
and:
Changes to this property can be animated. However, if the transform
property contains a non-identity transform, the value of the frame
property is undefined and should not be modified. In that case, you
can reposition the view using the center property and adjust the size
using the bounds property instead.
Instead of changing the frame size, change the bounds size.
I have a view hierarchy similar to the one in the image below (blue is the visible part of the scene):
So I have a UIScrollView with a lot of elements, out of which I am only showing the two button since they are relevant to the question. The first button is visible when the app is run, whereas the other one is positioned outside of the initially visible area. The first button is also the preferredFocusedView.
Now I am changing focus between the two buttons using a UIFocusGuide, and this works (checked it in didUpdateFocusInContext:). However, my scroll view does not scroll down when Button2 gets focused.
The scroll view is pinned to superview and I give it an appropriate content size in viewDidLoad of my view controller.
Any ideas how to get the scroll view to scroll?
Take a look at UIScrollView.panGestureRecognizer.allowedTouchTypes. It is an array of NSNumber with values based on UITouchType or UITouch.TouchType (depending on language version). By default allowedTouchTypes contains 2 values - direct and stylus. It means that your UIScrollView instance will not response to signals from remote control. Add the following line to fix it:
Swift 4
self.scrollView.panGestureRecognizer.allowedTouchTypes = [NSNumber(value: UITouchType.indirect.rawValue)]
Swift 4.2 & 5
self.scrollView.panGestureRecognizer.allowedTouchTypes = [NSNumber(value: UITouch.TouchType.indirect.rawValue)]
Also, don't forget to set a correct contentSize for UIScrollView:
self.scrollView.contentSize = CGSize(width: 1920.0, height: 2000.0)
Finally I solved this by setting scrollView.contentSize to the appropriate size in viewDidLoad.
You need to add pan gesture recognizer. I learned from here: http://www.theappguruz.com/blog/gesture-recognizer-using-swift. I added more code to make it not scrolling strangely, e.g. in horizontal direction.
var currentY : CGFloat = 0 //this saves current Y position
func initializeGestureRecognizer()
{
//For PanGesture Recoginzation
let panGesture: UIPanGestureRecognizer = UIPanGestureRecognizer(target: self, action: Selector("recognizePanGesture:"))
self.scrollView.addGestureRecognizer(panGesture)
}
func recognizePanGesture(sender: UIPanGestureRecognizer)
{
let translate = sender.translationInView(self.view)
var newY = sender.view!.center.y + translate.y
if(newY >= self.view.frame.height - 20) {
newY = sender.view!.center.y //make it not scrolling downwards at the very beginning
}
else if( newY <= 0){
newY = currentY //make it scrolling not too much upwards
}
sender.view!.center = CGPoint(x:sender.view!.center.x,
y:newY)
currentY = newY
sender.setTranslation(CGPointZero, inView: self.view)
}