XCode: Stack View breaking with custom control - ios

I am looking to layout multiple components in a stack view. The layout is working perfectly until I add a custom control, which is removing all elements below it in the stack. I'm not sure what could be causing this (perhaps it's from constraints?), any ideas would be appreciated!
Storyboard (the custom control is "Rating Control", in between the Name Text Field and the Photo Image View):
Simulator without custom control:
Simulator with custom control:

This was caused by the following function in my custom control:
override var intrinsicContentSize : CGSize {
let buttonSize = Int(frame.size.height)
let width = (buttonSize + spacing) * stars
return CGSize(width: width, height: buttonSize)
}

Related

how to change size of customView passed as UICalanderView Decoration?

I could not find much detail about how to add a customView as decoration for UICalenderView. There are many blogs telling how to add images but could not find anyone about CustomView. In images, we can return the decoration item with size parameter however in case of customView there is no option to pass size along with customView that you are adding. So in the end, I was able to add a view with red background, but the size is wrong. I tried to create a view and give it frame but it had no effect. So im confused how to adjust its size. Here is the method in which I add customView that im creating:
func calendarView(_ calendarView: UICalendarView, decorationFor dateComponents: DateComponents) -> UICalendarView.Decoration? {
return .customView(addActivityCircle)
}
And this is my addActivityCircle method which for now just creating a view with red background color:
private func addActivityCircle() -> UIView {
let view = UIView()
view.backgroundColor = .red
view.clipsToBounds = false
view.frame = CGRect(x: 0, y: 0, width: 50, height: 50)
return view
}
When I run this code I do see a view with red color but it's like a small rectangle, not 50x50. If I pass small values like 20x20, I do see a small rectangle but anything above that I see a rectangle of fixed size. I think that's the limit of decoration item but in apps like Fitness app by apple, there are bigger activity rings than that so there should be a way to have bigger sized custom views as this is just too small. The width is fine but the height is just too less. This is what im getting and it does not get any higher than that:

Cannot change the height of Login Button in FBSDKLoginKit?

I am using FBSDKLoginKit in iOS with Swift.
Up until recently it has been working perfectly, however I now cannot override the height of my button in the Storyboard?
The height of the button is now much smaller for some reason. I have tried setting height constraints for the button, putting the button in a stack view and set to fill proportionally and even override the button height in the SDK with no luck.
If I change the button to a normal UIButton the layout constraints work perfectly.
This is what the button looks like when I run the app.
This is how I would like the button to look - size wise.
I've also run into this problem. The reason for this is explained in the 4.18.0 to 4.19.0 upgrade guide:
The FBSDKLoginButton UI has changed in 4.19.0. Instead of "Log in with Facebook", the button now displays "Continue with Facebook". The button color is changed to #4267B2 from #3B5998. The button height is now fixed at 28 due to use of smaller font size and paddings around a larger Facebook logo.
The only workaround I found so far is to downgrade the SDK version to 4.18.0 (it did the job for me).
It is possible that FB will address this issue (...that they've created for many people) in one of the future updates to the SDK.
Towards a more permanent solution, we can see the specific changes that caused this, on GitHub. The change I find most suspicious starts on line 194:
[self addConstraint:[NSLayoutConstraint constraintWithItem:self
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1
constant:28]];
If the above constraint is removed/disabled, it could help reverse the situation. It should look approximately like this (I don't have an IDE at hand at the time of writing):
// Obtain all constraints for the button:
let layoutConstraintsArr = fbLoginButton.constraints
// Iterate over array and test constraints until we find the correct one:
for lc in layoutConstraintsArr { // or attribute is NSLayoutAttributeHeight etc.
if ( lc.constant == 28 ){
// Then disable it...
lc.active = false
break
}
}
When I get a chance to test the above or if I find a better solution, I'll update the answer.
You can conveniently achieve this with a simple override of the facebook button.
Swift:
class FacebookButton: FBSDKLoginButton {
override func updateConstraints() {
// deactivate height constraints added by the facebook sdk (we'll force our own instrinsic height)
for contraint in constraints {
if contraint.firstAttribute == .height, contraint.constant < standardButtonHeight {
// deactivate this constraint
contraint.isActive = false
}
}
super.updateConstraints()
}
override var intrinsicContentSize: CGSize {
return CGSize(width: UIViewNoIntrinsicMetric, height: standardButtonHeight)
}
override func imageRect(forContentRect contentRect: CGRect) -> CGRect {
let logoSize: CGFloat = 24.0
let centerY = contentRect.midY
let y: CGFloat = centerY - (logoSize / 2.0)
return CGRect(x: y, y: y, width: logoSize, height: logoSize)
}
override func titleRect(forContentRect contentRect: CGRect) -> CGRect {
if isHidden || bounds.isEmpty {
return .zero
}
let imageRect = self.imageRect(forContentRect: contentRect)
let titleX = imageRect.maxX
let titleRect = CGRect(x: titleX, y: 0, width: contentRect.width - titleX - titleX, height: contentRect.height)
return titleRect
}
}
In this code sample standardButtonHeight is a defined constant with the desired button height.
Also note that the logo size of 24.0 is the same size used in version 4.18 of the SDK.
As for now the Facebook button has only one constraint which is the height constraint and you can just remove all constraints of the button and add yours.
facebookSignInButton.removeConstraints(facebookSignInButton.constraints)
But of course this can change in the future and you might remove a constraint that you don't want to. Maybe a better solution would be if you remove only that problematic constraint.
if let facebookButtonHeightConstraint = facebookSignInButton.constraints.first(where: { $0.firstAttribute == .height }) {
facebookSignInButton.removeConstraint(facebookButtonHeightConstraint)
}
So I took #Dev-iL's solution and tweaked it to something a bit more future proof. I'm very new to this so it took me a few hours to figure it out, but I thought I'd share since it specifically deactivates the height constraint based on being a height constraint instead of based on the constant value.
I've used a subview classed as the Facebook button in my storyboard and have set the new constraint there.
I prefer this method and feel its a cleaner approach.
Note: I believe for a height constraint it will always be the first value however please correct me if I'm wrong and I'll update with an edit. As I mentioned I'm new to this
Edit: I decided to include the constant value of 28 to allow for my storyboard height constraint to be skipped during the removal. This isn't needed if you add the constraint programmatically after the removal
for const in fbLoginButton.constraints{
if const.firstAttribute == NSLayoutAttribute.height && const.constant == 28{
fbLoginButton.removeConstraint(const)
}
}
This little autoclosure in Swift 4.0 also works perfectly if you have no reason to remove the constraint, just override it.
let facebookLoginButton = FBSDKLoginButton()
if let constraint = facebookLoginButton.constraints.first(where: { (constraint) -> Bool in
return constraint.firstAttribute == .height
}) {
constraint.constant = 40.0
}
Or if you hate let statements:
let facebookLoginButton = FBSDKLoginButton()
facebookLoginButton.constraints.first(where: { (constraint) -> Bool in
return constraint.firstAttribute == .height
})?.constant = 40.0
If you are after just changing the height of your button, you can simply adjust the constant of the already present height constraint on the button, after adding the button in your Storyboard:
for constraint in facebookButton.constraints where constraint.firstAttribute == .height {
constraint.constant = YOUR_Height
}
This code can be placed in viewDidLoad().
I could manage to change the height of the button this way:
I added a view facebookButtonView to the storyboard with the size that i want, and then in the viewDidLoad i simple do this:
let loginButton = LoginButton(frame: self.facebookButtonView.frame, readPermissions: [ .publicProfile ])
self.view.addSubview(loginButton)
The Facebook button take the same size as the facebookButtonView. I tested with height 50 and it's working.
As a last resort, try implementing your own custom button to act as a Facebook Login button. They might be preventing the customization of the button from the SDK.
https://developers.facebook.com/docs/swift/login
-- There is a section here with example code - "Custom Login Button". It doesn't seem complicated.
We had the same problem. We solve this problem by creating the button in code with initWithFrame method.
from documentation
FBSDKLoginButton has a fixed height of #c 30 pixels, but you may change the width. initWithFrame:CGRectZero
will size the button to its minimum frame.
this solution is working for us
let facebookButton = FBSDKLoginButton(frame:facebookButtonPlaceholder.bounds)
facebookButton.readPermissions = ["email"]
facebookButton.backgroundColor = UIColor.clear
facebookButtonPlaceholder.addSubview(facebookButton)
facebookButtonPlaceholder.backgroundColor = UIColor.clear
Realising this is an old question, I've found a solution and am posting it as an answer for future reference.
After you have installed the FBSDKLoginKit via a Pod, look in your directory on the left in XCODE and open your PODS -- not your Podfile. Open FBSDKLoginKit, and open FBSDKLoginButton.m file.
You will now see an alert indicating that you are editing. Ignore the alert, that is take notice of the message and make sure that you don't change anything other than your target info. Change the two fields that dictate Facebook button height in the file as seen below:
Pictures to help you through the guide above:
project structure files to open
first field to change
second field to change
EASIEST SOLUTION, no need to deal with programmatic rects, just do it in storyboard
Autolayout does not work on Facebook button(FBSDKButton) anymore. I changed its height using buttons frame.
let fbLoginbutton = FBSDKLoginButton()
view.addSubview(fbLoginbutton)
fbLoginbutton.frame = CGRect(x: 38, y: 397, width: 300, height: 38)
You can set it according to your requirement. Although I'm still not able to change its font & Logo size.
Xamarin.iOS example using Linq.
I created the button in the storyboard file, and assigned a height there. However, I could not just remove all height constraints because the one I set in the storyboard was getting removed as well. I had to check to see if there is an existing height constraint of size 28 - and remove that one
var contraint = this.FacebookLoginButton?.Constraints?.Where(x => x.FirstAttribute.Equals(NSLayoutAttribute.Height) && x.Constant == 28f)?.FirstOrDefault() ?? null;
if (contraint != null)
{
this.FacebookLoginButton.RemoveConstraint(contraint);
}

Get the intrinsic height of a custom control

How can I get the height of my custom control?
The idea is I will use it to dynamically set the height of some buttons inside the custom control. I've set the Placeholder height to 44 in the Xcode size inspector.
Working off Apple's Start Developing iOS Apps (Swift) tutorial, I am attempting to access frame.size.height and it gives a value of 1000 while the tutorial seems to suggest it should be 44.
class RatingControl: UIView {
...
override public var intrinsicContentSize: CGSize {
let buttonSize = Int(frame.size.height)
print(buttonSize) // prints 1000
let width = (buttonSize * starCount) + (spacing * (starCount - 1))
return CGSize(width: width, height: buttonSize)
}
...
You should never access frame inside intrinsicContentSize. intrinsicContentSize should return the size that perfectly fits the contents of the view, regardless of its current frame.
In your case, I think you can just use 44 for your buttonSize.
The placeholder intrinsic size is just that, placeholder, so that IB interpreter is has some value to work with and can layout the rest of the scene. But in your intrinsicContentSize getter, you implement the real size, which will be used in runtime by the AutoLayout engine. Since you return 1000 as the intrinsic content height, that's what you will see in runtime.

How to center the subviews of UIScrollView

I'm a beginner in creating a custom view. I'm trying to create a custom UIView with a scrollview and buttons that will look like this:
I'm adding a view(view with label of page number) inside of scrollView depending on the the number of pages. Is that how it should be?
Currently it looks like this:
My question is how can I center the subviews of scrollview? and next is what's wrong with this code? Why is that I can only see 1 label inside the view? and the other doesn't show up. How can I scroll to the selected page if the page number is not visible already in the scrollview?
Here's my code:
func addPageNumberViewWithCount(count: Int) {
var pageNumberViewX: CGFloat! = 0
let pageNumberViewDistance: CGFloat! = 10
for i in 1...count {
let pageNumberView = UIView(frame: CGRectMake(pageNumberViewX, 0, 30, 30))
pageNumberView.backgroundColor = UIColor.redColor()
pageNumberView.layer.cornerRadius = pageNumberView.frame.height / 2
pageNumberView.layer.masksToBounds = true
pageNumberView.clipsToBounds = true
// add number label
let label = UILabel(frame: CGRectMake(pageNumberViewX, 0, 30, 30))
label.center = pageNumberView.center
label.text = "\(i)"
label.textAlignment = .Center
pageNumberView.addSubview(label)
// update x for next view
pageNumberViewX = pageNumberView.frame.origin.x + pageNumberView.frame.width + pageNumberViewDistance
// add view inside scrollview
scrollView.addSubview(pageNumberView)
if i == count {
scrollView.contentSize = CGSizeMake(pageNumberViewX + pageNumberView.frame.width, 30)
}
}
}
Part of my answer will go to providing a solution to your question,and another part of my answer will go toward strongly suggesting that this not be the method you use to complete your desired tasks.
At this point, AutoLayout and Interface Builder have come a long way. Where they used to be difficult to use because of their inconsistency and unpredictability, they are now highly predictable and consistent as long as you understand the tools and how to use them.
Apple's suggested method for completing this task (which I mostly stand behind) is creating a .xib file (nib) to lay out the base components of the design, and to load the nib into the view or view controller whenever that design should be used. My question for you: have you tried this, or have you determined for some reason that this would be an unsatisfactory solution to your problem? AutoLayout exists to solve these problems not just in allowing you to achieve your desired solution in this one situation but to achieve it in other situations as well, with varying screen sizes and device types.
Now, if you were to simply ignore all of that and continue on your path, there would be a few good ways to handle your problem. One suggested solution I have:
1) Wrap your pageNumberView in another view. Constrain that view to the size of the scrollView. Doing this gives the scrollView content with which to base its scrollable content size, and gives the inner pageNumberView something to compare itself to.
2) Center the pageNumberView horizontally in its container (the new view that we just created).
Doing this, the page numbers should now center themselves in the container until they reach a size where they exceed the width of the scrollView. At that point, they will then continue to expand, making the area horizontally scrollable.
I can provide code examples of how you would do this, but frankly I would much prefer if you scrapped the idea of doing things this way and instead opted for the AutoLayout method at least, and perhaps even the Interface Builder method. I started out with iOS the same way you did, trying to do everything in code. It really isn't the best way to do things, at least with regard to iOS.
Edit: I've provided an example of how this would look in Interface Builder using UINib. I've populated the view with an example of 5 pages to show what it is like. I will see if I can make a GIF or something similar to show what each of the subviews look like.
For the OP, my suggestion would be this: Use this for reference, and go learn the constraints system. It is extremely unlikely that you will find success with iOS if you do not learn and utilize the constraints system. Coding in X values to a UIView's frame is only going to create a product with poor, inconsistent performance across devices, and will take much, much longer than it would to take the time to learn constraints.
Perhaps you should have a UICollectionView with a cell for each of these buttons. That's a better way of doing this, and you can lay it out again when the screen rotates and it changes width.
Those cells will layout offset to the left. You can solve that this way:
let pageNumberViewTotalWidth = 30 * count + (pageNumberViewDistance * count - 1)
self.collectionView.contentInset.left = (self.collectionView.frame.size.width - pageNumberViewTotalWidth) / 2
The labels aren't showing up because you're setting their frame's x to be the same as the page number view's x. It's frame should be relative to it's superview, in this case pageNumberView.
First Question of yours "how can I center the subviews of scrollview?"
Solution: lets suppose you have in total 50 pages and you want to show 5 pages at a time in the scrollview.
Then make 10 subviews of equal widths where each subview width will be equal to visible portion of the collection view that is
self.view.size.width - 2*(width of toggle button)
Then in each container view add 5 of your pageNumberView placed at equal distance
lets pageNumberViewWidth = container.width/5 - 2*margin
now pageNumberView frame will be (margin,0,pageNumberViewWidth,height)
In this way in each container view your pageNumberViews will be placed equally and it will look as if you have centred them.
Second Question "Why is that I can only see 1 label inside the view?"
Answer : Its because you are setting label frame incorrectly
let label = UILabel(frame: CGRectMake(pageNumberViewX, 0, 30, 30))
Here label is the subview of pageNumberView So you have to set its frame according to its parent's view which is pageNumberView, so change it to
let label = UILabel(frame: CGRectMake(0, 0, 30, 30))
First time it was right because pageNumberViewX is 0 for first iteration after that it become some positive value which makes its frame shifted to right but its parent's width is small so its not visible to you.
Third Question : "How can I scroll to the selected page if the page number is not visible already in the scrollview?"
For this you need to find the frame of your selected page:
you can do that by using the offset that you used to create pageNumberView.
(width of each pageNumberView)*pageNumber = starting point of the required pageNumberView.
let frame : CGRect = CGRectMake(calculated offset above, 0,30, 30)
//where you want to scroll
self.scrollView.scrollRectToVisible(frame, animated:true)
I hope this will help you in solving your problem
Edit for first problem
func addPageNumberViewWithCount(count: Int) {
var containerViewX: CGFloat! = 0
let pageNumberViewDistance: CGFloat! = 10
let pageNumberViewPerSubview = 5
var numberOfSubview = count/pageNumberViewPerSubview
if(count % pageNumberViewPerSubview > 0){
numberOfSubview = numberOfSubview + 1
}
var pagesLeft = count
for i in 1...numberOfSubview {
var pageNumberViewX: CGFloat! = 0
let containerView : UIView = UIView(frame:CGRectMake(containerViewX,0,scrollView.frame.size.width,scrollView.frame.size.height))
if(pagesLeft < pageNumberViewPerSubview){
for k in 1...pagesLeft{
}
}
else{
for j in 1...pageNumberViewPerSubview{
let pageNumberView = UIView(frame: CGRectMake(pageNumberViewX, 0, 30, 30))
pageNumberView.backgroundColor = UIColor.redColor()
pageNumberView.layer.cornerRadius = pageNumberView.frame.height / 2
pageNumberView.layer.masksToBounds = true
pageNumberView.clipsToBounds = true
// add number label
let label = UILabel(frame: CGRectMake(0, 0, 30, 30))
label.text = "\(i)"
label.textAlignment = .Center
pageNumberView.addSubview(label)
// update x for next view
pageNumberViewX = pageNumberView.frame.origin.x + pageNumberView.frame.width + pageNumberViewDistance
containerView.addSubview(pageNumberView)
}
containerViewX = containerViewX + scrollView.frame.size.width
// add view inside scrollview
scrollView.addSubview(containerView)
pagesLeft = pagesLeft - pageNumberViewPerSubview
}
if i == count {
scrollView.contentSize = CGSizeMake(numberOfSubview*scrollView.frame.size.width, 30)
}
}
}

How to make UIView change it size as it scrolls

I am implementing a scroll view picker I have implemented most of the part but I'm stuck at changing the size when it scrolls.
I'm trying to do this :
I have got till here and i don't know how to change size using scroll offset i tried many things but failed i need help
I'm using SwipeView to get the scroll
func swipeViewDidScroll(swipeView: SwipeView!) {
//USE swipeView.currentItemView to access current view
//USE swipeView.itemViewAtIndex(swipeView.currentItemIndex - 1) to access previous view
//USE swipeView.itemViewAtIndex(swipeView.currentItemIndex + 1) to access next View
swipeView.currentItemView.frame = CGRect(x: swipeView.currentItemView.bounds.origin.x, y: swipeView.currentItemView.bounds.origin.y , width: (swipeView.currentItemView.bounds.width + swipeView.scrollOffset) - CGFloat((280 * swipeView.currentItemIndex)), height: swipeView.currentItemView.bounds.height)
}
I have attached the project to make your work easier understanding my work
LINK TO PROJECT
What about this:
create a special class for the cells in the slider
when the cell is created, add NSLayoutContraints of width and aspect ratio to the view
create a method to expand the cell using the constraint inside the class, like
-(void)expandCell {
self.widthConstraint.constant = normalWidth * 1.05f;
}
-(void)normalizeCell {
self.widthConstraint.constant = normalWidth;
}
when the view is at the center you call the method expandCell to expand it 5% and when the cell is out you call the normalizeCell. To make things prettier you just animate that.

Resources