Ok, I have a super weird issue. I have a UIScrollView that I'm using to update the position of a second UIView. So, when you scroll the UIScrollView, the UIView moves as well at a proportional but slower speed. The code is pretty simple.
Scroller.Scrolled += delegate {
if (Scroller.ContentOffset.Y <= 0) {
filtersTop.Constant = 0;
} else {
filtersTop.Constant = -(Scroller.ContentOffset.Y / 2);
}
}
filtersTop is a NSLayoutConstraint. Scroller is the UIScrollView.
The problem is whenever I set filtersTop.Constant to any value derived from Scroller.Content.Y, it resets Scroll.ContentOffset to 0 in iOS7 only. This code works fine in iOS8+. I've tried a dozen variations on this. If I set filtersTop.Constant to a static number (ie: 123.5), that works.
I've tried saving the value to a variable and forcing the type thinking maybe it was getting cast improperly. If I trace out the value, it works. But, the minute I set it to filtersTop.Constant, it resets Scroller.ConstentOffset.Y again. Scroller and filters are NOT related. They are not nested or associated with each other in any way. So, I have no idea why setting the constant on the constraint on filters would in any way affect Scroller.
Anyone know what is happening here?
Ok I had another look it seems to be resetting the contentSize between calling ViewWillLayoutSubviews and calling ViewDidLayoutSubviews
ContentSize set to: {Width=320, Height=3000}
Called ViewWillLayoutSubviews
ContentSize set to: {Width=320, Height=3000}
ContentSize set to: {Width=0, Height=0}
Called ViewDidLayoutSubviews
I then subclassed the scrollview and forced the contentSize to stay the same like so:
public class CustomScroller : UIScrollView
{
public CustomScroller ()
{
ContentSize = new CGSize(320,3000);
}
public override CGSize ContentSize {
get {
return base.ContentSize;//new CGSize(320,3000);
}
set {
base.ContentSize = new CGSize(320,3000);
Console.WriteLine ("ContentSize set to: "+ContentSize);
}
}
public override void LayoutSubviews ()
{
Console.WriteLine ("LayoutSubviews: "+Frame.Y);
base.LayoutSubviews ();
}
}
You will then have to set the contentSize when the view loads rather than in ViewDidLayoutSubviews.
I have updated the code here with the solution detailed above and here is the result.
Note I changed the scrolled code but it will work with the original too.
This works in iOS7 and iOS8. Hope this helps!
Related
class ApplyCorners: UIButton {
override func didMoveToWindow() {
self.layer.cornerRadius = self.frame.height / 2
}
}
I apply this class to the buttons in my application and it is working great, but when I apply it to a button used in every cell in a table view the button corners are not round upon entering the view, but if I click one of the buttons I get segued to another view. If I then segue back the corners are "fixed" / round.
The green is the button when returning and the red is upon first entering the view.
Anyone know how to fix this?
I'd suggest layoutSubviews, which captures whenever the frame of the button changes:
class ApplyCorners: UIButton {
override func layoutSubviews() {
super.layoutSubviews()
layer.cornerRadius = frame.height / 2
}
}
This takes care of both the original appearance and any subsequent appearance. It also avoids all sorts of problems related to not only whether the frame was known when the view appeared, but also if you do anything that might change the size of the button (e.g. anything related to constraints, rotation events, etc.).
This sort of thing is likely to be a timing problem. Consider the phrase self.frame.height. At the time didMoveToWindow is called, we may not yet know our frame. If you are going to call a method that depends upon layout, do so when layout has actually occurred.
Gonna propose another alternative: listen to any bounds changes. This avoids the problem of wondering "is my frame set yet when this is called?"
class ApplyCorners: UIButton {
override var bounds: CGRect {
didSet {
layer.cornerRadius = bounds.height / 2
}
}
}
Edited frame to bounds because as #Rob points out, listening for frame changes will cause you to miss the initial load sometimes.
Putting your code in didMoveToWindow() does not make sense to me. I'd suggest implementing layoutSubviews() instead. That method gets called any time a view object's layout changes, so it should update if you resize your view.
(Changed my suggestion based on comments from TNguyen and and Rob.)
With my autolayout, I am trying to implement a special case for the smallest screens, when my icons need to be smaller. I am changing the constraint for the icons in traitCollectionDidChange(). However, the change has no effect. I've tried adding SetNeedsLayout, SetNeedsDisplay, UpdateConstraints, etc, and nothing works.
If I change these constraints when the view is initialized, then the change works -- but that doesn't help me when the user rotates the device and I need the change to happen again. However, it's interesting to note that traitCollectionDidChange() is called at the launch of the program, but if I make the constraint changes within that function, they don't work. They only work if I set the new constraints in the init() function of the view.
It could be that this function is called AFTER the constraints are used, except Apple tells us to use this function for just this purpose, to change layouts etc when rotating. I don't want to use WillTransitionToSize because only the ViewController is called with that function, not views. But I will probably try that next.
traitCollectionDidChange() is definitely being called, at startup and when I rotate. It's just that my changes don't do anything. Any ideas?
Any ideas?
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
// smaller icon constraints for iPhone 5/SE
if let constraint = (self.imageView!.constraints.filter{$0.firstAttribute == .width}.first) {
print("cwide \(constraint.constant)")
if UIScreen.main.bounds.size.width <= 320 {
constraint.constant = 20.0
self.radius = 4
}
else {
constraint.constant = 32.0
self.radius = 8
}
}
self.updateConstraints()
}
I had a similar feature and i turned out that I had forgotten to install the constraints in the Interface Builder.
I'm learning swift with cs193p and I have a problem with UITextView.sizeThatFits(...). It should return a recommended size for popover view to display an [int] array as a text. As you can see in Paul Hegarty's example (https://youtu.be/gjl2gc70YHM?t=1h43m17s), he gets perfectly-fit popover window without scrollbar. I'm using almost the same code that was in this lecture, but instead i've got this:
the text string equals [100], but the sizeThatFits() method is returning a size that is too small to display it nicely, even though there is plenty of free space.
It is getting a bit better after I've added some text, but still not precise and with the scrollbar:
Here is the part of the code where the size is being set:
override var preferredContentSize: CGSize {
get {
if textView != nil && presentingViewController != nil {
// I've added these outputs so I can see the exact numbers to try to understand how this works
print("presentingViewController!.view.bounds.size = \(presentingViewController!.view.bounds.size)")
print("sizeThatFits = \(textView.sizeThatFits(presentingViewController!.view.bounds.size))")
return textView.sizeThatFits(presentingViewController!.view.bounds.size)
} else { return super.preferredContentSize }
}
set { super.preferredContentSize = newValue }
}
What should I do so this will work in the same way as in the lecture?
It looks like there are 16 pt margins between the label and its parent view. You need to take that into account when returning the preferred size of the popover.
You should try both of the following:
Add 32 to the width that's returned from preferredContentSize
In Interface Builder, clear the layout constraints on your UILabel, then re-add top, bottom, leading, and trailing constraints and make sure that "Constrain to Margins" option is not enabled.
Finally, instead of overriding preferredContentSize, you can simply set the preferredContentSize when your view is ready to display, and you can ask Auto Layout to choose the best size:
override func viewDidLayoutSubviews() {
self.preferredContentSize = self.view.systemLayoutSizeFitting(UILayoutFittingCompressedSize)
}
If your layout is configured correctly, systemLayoutSizeFitting(UILayoutFittingCompressedSize) will return the smallest possible size for your view, taking into account all of the margins and sub-views.
i'm trying to add a sublayer behind the imageView however the issue is that since it is using constraints it can't seem to figure out the position and just places sublayer in left corner? i've tried to add the LFTPulseAnimation to viewDidLayoutSubViews but then everytime i reopen the app it will add one on top.
viewDidLoad
//GroupProfile ImageView
imageGroupProfile = UIImageView(frame: CGRect.zero)
imageGroupProfile.backgroundColor = UIColor.white
imageGroupProfile.clipsToBounds = true
imageGroupProfile.layer.cornerRadius = 50
self.view.addSubview(imageGroupProfile)
imageGroupProfile.snp.makeConstraints { (make) -> Void in
make.height.equalTo(100)
make.width.equalTo(100)
make.centerX.equalTo(self.view.snp.centerX)
make.centerY.equalTo(self.view.snp.centerY).offset(-40)
}
let pulseEffect = LFTPulseAnimation(repeatCount: Float.infinity, radius:160, position:imageGroupProfile.center)
self.view.layer.insertSublayer(pulseEffect, below: imageGroupProfile.layer)
i've tried to add the LFTPulseAnimation to viewDidLayoutSubViews but then everytime i reopen the app it will add one on top.
Nevertheless that is the way to do it. Just add a Bool property so that your implementation of viewDidLayoutSubViews inserts the layer only once:
var didLayout = false
override func viewDidLayoutSubviews() {
if !didLayout {
didLayout = true
// lay out that layer here
}
}
The reason is that you don't have the needed dimensions until after viewDidLayoutSubviews tells you that (wait for it) your view has been laid out! But, as you rightly say, it can be called many times subsequently, so you also add the condition so that your code runs just once, namely the first time viewDidLayoutSubviews is called.
I am porting an app from Ipad to mac. (I know that it sounds weird)
I stuck with NSScrollview. Please guide me contentsize , contentOffset equivalent in NSScrollview.
UIScrollView* uiScroll;
uiScroll.contentSize;
uiScroll.contentOffset;
uiScroll.contentSize = CGSizeMake(w,h);
uiScroll.contentOffset = CGPointMake(x,y);
=
NSScrollView* nsScroll;
nsScroll.documentView.frame.size;
nsScroll.documentVisibleRect.origin;
nsScroll.documentView.frameSize = NSMakeSize(w,h);
[nsScroll.documentView scrollPoint:NSMakePoint(x,y)];
Or perhaps even better:
import AppKit
extension NSScrollView {
var documentSize: NSSize {
set { documentView?.setFrameSize(newValue) }
get { documentView?.frame.size ?? NSSize.zero }
}
var documentOffset: NSPoint {
set { documentView?.scroll(newValue) }
get { documentVisibleRect.origin }
}
}
Notes: I used 'documentSize' (and 'documentOffset') because 'contentSize' conflicts with an already existing property of NSScrollView.
In addition to the lines from #aepryus, here are a couple more useful lines for getting/setting the scroll offset on macOS:
//Get the current scroll offset:
_contentViewOffset = scrollView.contentView.bounds.origin;
//Set the scroll offset from the retrieved point:
NSPoint scrollPoint = [scrollView.contentView convertPoint:_contentViewOffset toView:scrollView.documentView];
[scrollView.documentView scrollPoint:scrollPoint];
Everything you need to know about NSScrollView is laid out in the Scroll View Programming Guide for Cocoa provided in the documentation.
Although it doesn't appear that there's a direct equivalent, UIScrollView's contentSize can be likened to the size of NSScrollView's documentView, which is the scrollable content provided as an NSView to NSScrollView with setDocumentView:.
setContentOffset: can be compared to NSView's scrollPoint:, which uses an NSPoint to specify the offset of the documentView within the NSScrollView.
See the documentation for elaboration and code examples.