center element when pressed in a ScrollView - ios

i really how to do a thing wich is probably easy..
I've a ScrollView with some button in it.
That's how i create all my button in the scrollview.
var buttonList = [UIButton]()
func createButton() {
let imageArray = fillImageArray()
var lastButtonWidth: CGFloat = 0
for index in 0..<6 {
let frame1 = CGRect(x: ((self.view.frame.size.width / 2) - 27.5) + CGFloat(index * 70), y: 0, width: 55, height: 55 )
let button = UIButton(frame: frame1)
button.setImage(imageArray[index], forState: .Normal)
button.tag = index
button.addTarget(parentViewController, action: #selector(ViewController.buttonClicked(_:)), forControlEvents: .TouchUpInside)
self.scrollView.addSubview(button)
lastButtonWidth = frame1.origin.x
buttonList.append(button)
}
self.scrollView.contentSize = CGSizeMake(lastButtonWidth + 55, 0)
}
I want when i press one of my button to center him and positioning correctly the other buttons.
example :
If i press on 5 i want this result :
the button 5 is moved to the center.

Now what I would suggest using is the scroll view method scrollView.scrollRectToVisible(CGRect, animated: Bool). This will move the scroll view to make a certain part of your content visible.
To create the CGRect you could do something like this:
let scrollWidth = scrollView.frame.width
let scrollHeight = scrollView.frame.height
let desiredXCoor = button.frame.origin.x - ((scrollWidth / 2) - (button.frame.width / 2) )
let rect = CGRect(x: desiredXCoor, y: 0, width: scrollWidth, height: scrollHeight)
scrollView.scrollRectToVisible(rect, animated: true)
My math may be a bit off, but the essence is that you use the size of the scrollView and the UIButton to create a CGRect for the scroll view to move to. This means when a button is clicked, you could use an implementation like this:
func myMethod() {
button.addTarget(self, action: "buttonClicked:", forControlEvents: UIControlEvents.TouchUpInside)
}
func buttonClicked(sender: UIButton){
let scrollWidth = scrollView.frame.width
let scrollHeight = scrollView.frame.height
let desiredXCoor = sender.frame.origin.x - ((scrollWidth / 2) - (sender.frame.width / 2) )
let rect = CGRect(x: desiredXCoor, y: 0, width: scrollWidth, height: scrollHeight)
scrollView.scrollRectToVisible(rect, animated: true)
}
If adjusted properly to your project, this should allow you to do what you have outlined.

Hope this would help you.
In button action method write below code.
func buttonClicked(sender:UIButton) {
for view in self.scrollView.subviews {
if view.isKindOfClass(UIButton) && view.tag == sender.tag {
let xCenter = max(0, (view.center.x - self.scrollView.frame.width/2))
self.scrollView.setContentOffset(CGPointMake(xCenter, self.scrollView.contentOffset.y), animated: true)
break;
}
}
}

I suppose you want the button touched to be placed in the center,so there is a easy solution, you can use the button's center.x - scrollview.width / 2 as the offsetX to construct the contentOffset and take two boundary situation into consideration:offsetx < 0 and offsetx + scrollview.width > scroll.contentSize.width

Ok i just found by myself.
let centerScrollView = scrollView.frame.size.height * 2
UIView.animateWithDuration(0.2, delay: 0, options: UIViewAnimationOptions.CurveLinear, animations: {
self.scrollView.contentOffset = CGPoint(x: sender.frame.origin.x - centerScrollView, y: sender.frame.origin.y)
}, completion: nil)
and it's perfectly center. Thx everyone.

Related

How to dynamically create multiple buttons after the text of a UITextView

I'm trying to create a small choose your own adventure game. After the text of each page I want to have an area for different options the player can choose. The buttons need to be displayed after all the text and not be visible until the player reads (or scrolls down) all the text.
Here's how I'm trying to create the buttons:
func CreateButtons(buttons: Int, loadedQuest: XML) {
let buttonHeight: CGFloat = 44
let contentInset: CGFloat = 8
//inset the textView
txtQuestText.textContainerInset = UIEdgeInsets(top: 0, left: 0, bottom: (buttonHeight+contentInset*2), right: 0)
for i in 1...buttons{
let button = UIButton(frame: CGRect(x: contentInset, y: (txtQuestText.contentSize.height - (contentInset * CGFloat(i))) + (buttonHeight * CGFloat(i)) - contentInset, width: txtQuestText.contentSize.width-contentInset*2, height: buttonHeight))
//setup your button here
button.setTitle("Option \(i)", for: .normal)
button.setTitleColor(UIColor.black, for: .normal)
button.backgroundColor = UIColor.darkGray
//button.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
//Add the button to the text view
txtQuestText.addSubview(button)
}
}
Here's how it's currently looking(make sure to scroll all the way down) The buttons are somehow being created outside the scroll range of the textview. How would I fix this and add padding between the buttons?
First you have to check the scroll view is at the bottom or not?
if it is at the end/bottom you may add buttons
this will help you to check it is at the end or not
extension UIScrollView {
var isAtTop: Bool {
return contentOffset.y <= verticalOffsetForTop
}
var isAtBottom: Bool {
return contentOffset.y >= verticalOffsetForBottom
}
var verticalOffsetForTop: CGFloat {
let topInset = contentInset.top
return -topInset
}
var verticalOffsetForBottom: CGFloat {
let scrollViewHeight = bounds.height
let scrollContentSizeHeight = contentSize.height
let bottomInset = contentInset.bottom
let scrollViewBottomOffset = scrollContentSizeHeight + bottomInset - scrollViewHeight
return scrollViewBottomOffset
}
}
add this extension
Hopeful it will work

Why button appears only on the second attempt?

I'm trying to add a function that will show the close button in my custom alert view.
When I call it for the first time, only an empty area appears that does not contain a button (no color, no text and no pressing). When you call again - everything works as it should.
It does not depend on the parameter animated, in both cases it works the same.
My dialogView is a my custom UIView. I don't use UIAlertController
What could be the problem?
initial view after the first attempt second attempt
GIF call func by click
func showCloseButton(text: String, animated: Bool){
let dialogViewHeight = self.dialogView.frame.height + 50 + 1
let button = UIButton(type: .roundedRect)
button.backgroundColor = .red
button.frame.origin = CGPoint(x: 0, y: dialogViewHeight)
button.frame.size = CGSize(width: self.dialogView.frame.width, height: 50)
button.setTitle(text, for: .normal)
button.addTarget(self, action: #selector(closeTapped), for: .touchUpInside)
let separatorLineView = UIView()
separatorLineView.frame.origin = CGPoint(x: 0, y: self.dialogView.frame.height)
separatorLineView.frame.size = CGSize(width: dialogView.frame.width, height: 1)
separatorLineView.backgroundColor = UIColor.groupTableViewBackground
dialogView.addSubview(separatorLineView)
if animated {
animateAdjustDialogView(height: dialogViewHeight){
Logger.Log("completion did")
self.dialogView.addSubview(button)
}
} else {
Logger.Log("button.frame.origin = \(button.frame.origin)")
Logger.Log("button.frame.size = \(button.frame.size)")
Logger.Log("button.title = \(button.titleLabel)")
self.dialogView.frame.size = CGSize(width: self.dialogView.frame.width, height: dialogViewHeight)
self.dialogView.addSubview(button)
}
}
private func animateAdjustDialogView(height: CGFloat, completion: (()->Void)?){
UIView.animate(withDuration: 1.0, animations: {
self.dialogView.frame.size = CGSize(width: self.dialogView.frame.width, height: height)
self.layoutIfNeeded()
}) { (finished) in
Logger.Log("finished = \(finished)")
if finished {
Logger.Log("completion run")
completion?()
}
}
}
Button frame is causing the issue -
button.frame.origin = CGPoint(x: 0, y: dialogViewHeight)
Where
let dialogViewHeight = self.dialogView.frame.height + 50 + 1
That means, button goes beyond dialogView frame.
So replace
button.frame.origin = CGPoint(x: 0, y: dialogViewHeight)
With
button.frame.origin = CGPoint(x: 0, y: dialogViewHeight - 50) // Height of the Button.
Let me know if you are still having any issue.

Swift 4 - Button over Tab Bar Item

I am trying to position a custom button over one of the item of my Tab bar.
func setupMiddleButton() {
let numberOfItems = CGFloat(tabBar.items!.count)
let tabBarItemSize = CGSize(width: tabBar.frame.width / numberOfItems, height: tabBar.frame.height)
let menuButton = UIButton(frame: CGRect(x: 0, y: 0, width: tabBarItemSize.width, height: self.tabBar.frame.size.height))
var menuButtonFrame = menuButton.frame
menuButtonFrame.origin.y = self.view.bounds.height - menuButtonFrame.height
menuButtonFrame.origin.x = self.view.bounds.width/2 - menuButtonFrame.size.width/2
menuButton.frame = menuButtonFrame
menuButton.backgroundColor = UIColor.white
self.view.addSubview(menuButton)
self.view.layoutIfNeeded()
My issue is that with the previous code the button is not perfectly over the bar item (see picture):
Any suggestion? I really don't know how else to try.
Thank you!
I notice that this screenshot is of an iPhone X Simulator, which has a different layout at the bottom of the screen.
Your code works well on any other iPhone. In iOS 11 they introduced what's called the "Safe area". When you calculate the size and origin for your button, you will have to take that into account.
When you calculate the origin.y for your buttonFrame, you have to subtract the height for the safe-area at the bottom, like this:
menuButtonFrame.origin.y = self.view.bounds.height - menuButtonFrame.height - self.view.safeAreaInsets.bottom
This won't solve your problem though, as your code probably runs in viewDidLoad, which happens before the view knows it's supposed be displayed on an iPhone X with a safe area.
You can override viewDidLayoutSubviews for this, and set the correct frame for your button each time that is called.
This will fix your issue:
class CustomTabBarViewController: UITabBarController {
let menuButton = UIButton(frame: CGRect.zero)
override func viewDidLoad() {
super.viewDidLoad()
setupMiddleButton()
}
func setupMiddleButton() {
let numberOfItems = CGFloat(tabBar.items!.count)
let tabBarItemSize = CGSize(width: tabBar.frame.width / numberOfItems, height: tabBar.frame.height)
menuButton.frame = CGRect(x: 0, y: 0, width: tabBarItemSize.width, height: tabBar.frame.size.height)
var menuButtonFrame = menuButton.frame
menuButtonFrame.origin.y = self.view.bounds.height - menuButtonFrame.height - self.view.safeAreaInsets.bottom
menuButtonFrame.origin.x = self.view.bounds.width/2 - menuButtonFrame.size.width/2
menuButton.frame = menuButtonFrame
menuButton.backgroundColor = UIColor.green
self.view.addSubview(menuButton)
self.view.layoutIfNeeded()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
menuButton.frame.origin.y = self.view.bounds.height - menuButton.frame.height - self.view.safeAreaInsets.bottom
}
}
I know it's tempting to simply call setupMiddleButton from inside viewDidLayoutSubviews, but do not do that. viewDidLayoutSubviews should not be used to create buttons etc., it should only be used to move them accordingly to the rest of the view. You might want to set the entire frame of menuButton inside viewDidLayoutSubviews rather than only the origin.y like I did, especially if you need to support rotation/landscape-mode. In this very simple example, updating origin.y is enough.

How can I properly place a UIButton on top of a loaded SFSafariViewController?

I have read many articles about the SFSafariViewController and I believe that it offers splendid functionality in iOS apps. However, when I load my SFSafariViewController, I intentionally hide the navigation bar because I want one custom fixed button in the upper left corner to dismiss the view controller.
override func viewDidAppear(_ animated: Bool) {
let safariViewController = PSSafariViewController(url: URL(string: blogUrl)!, entersReaderIfAvailable: true)
present(safariViewController, animated: false) {
var frame = safariViewController.view.frame
let OffsetY: CGFloat = 44
frame.origin = CGPoint(x: frame.origin.x, y: frame.origin.y - OffsetY)
frame.size = CGSize(width: frame.width, height: frame.height + OffsetY)
safariViewController.view.frame = frame
let btn: UIButton = UIButton(frame: CGRect(x: 100, y: 400, width: 100, height: 50))
btn.backgroundColor = UIColor.green
btn.setTitle("Click Me", for: .normal)
btn.addTarget(self, action: #selector(PSBlogViewController.buttonAction), for: UIControlEvents.touchUpInside)
btn.tag = 1 // change tag property
btn.isOpaque = true
safariViewController.view.addSubview(btn)
safariViewController.view.bringSubview(toFront: btn)
print(btn.description)
}
}
As you can see, I alter the frame so that the bar at the top is not visible. That code runs fine. But when I try to add a UIButton, it appears briefly and then is covered when I run the app. It's a simple blog reader app that uses the SFSafariViewController. Maybe Apple doesn't want developers running around messing with this, but any solutions or workarounds to make the button stay visible are greatly appreciated!
Here's the info about the button: 0x7f950b618db0; frame = (100 400; 100 50); tag = 1; layer = CALayer: 0x60000023ab60
Why don't you use one of the other WebView classes to get the extra functionalities you desire?
5.1.1 (iv) SafariViewContoller must be used to visibly present information to users; the controller may not be hidden or obscured by
other views or layers. Additionally, an app may not use
SafariViewController to track users without their knowledge and
consent.
It is definitely true that Apple won't want the SFSafariViewController to be obscured. However, I did figure out that when I present it, in the completion block I can add a button and what was causing trouble was that I had to increase the zPosition of the layer of the button's view like so:
present(safariViewController, animated: false) {
var frame = safariViewController.view.frame
let OffsetY: CGFloat = 44
frame.origin = CGPoint(x: frame.origin.x, y: frame.origin.y - OffsetY)
frame.size = CGSize(width: frame.width, height: frame.height + OffsetY)
safariViewController.view.frame = frame
self.btn = UIButton(frame: CGRect(x: 5, y: 50, width: 50, height: 50))
self.btn.layer.cornerRadius = 25
self.btn.backgroundColor = UIColor(red: 0, green: 170/255, blue: 240/255, alpha: 0.5)
self.btn.setTitle("←", for: .normal)
self.btn.addTarget(self, action: #selector(safariViewController.buttonAction(sender:)), for: .touchUpInside)
self.btn.tag = 1 // change tag property
self.btn.isOpaque = true
safariViewController.view.addSubview(self.btn)
safariViewController.view.bringSubview(toFront: self.btn)
self.btn.layer.zPosition = safariViewController.view.layer.zPosition + 1
for subview in safariViewController.view.subviews {
subview.isUserInteractionEnabled = false
}
self.btn.isEnabled = true
self.btn.isUserInteractionEnabled = true
//print(self.btn.description)
}

Create an "add-player" view - Swift

I'm creating a game where the player has the possibility to add several players on a view controller :
Actually, I can add a new player and remove it :
The problem appears when I remove a player :
I want player 2 and player 3 goes below the textField "Name when I remove a player. How can I do this ?
ViewDidLoad :
// MARK: ADD BUTTON
let addButtonSize = CGSize(width: topPartSize.height, height: topPartSize.height)
let addButton = UIButton(frame: CGRect(x: enterPlayerPartSize.width - addButtonSize.width, y: 0, width: addButtonSize.width, height: addButtonSize.height))
addButton.titleLabel!.font = UIFont.fontAwesomeOfSize(addButtonSize.height / 2)
addButton.setTitle(String.fontAwesomeIconWithName(.Plus), forState: .Normal)
addButton.setTitleColor(UIColor.whiteColor(), forState: .Normal)
addButton.setTitleColor(UIColor(red: 1, green: 1, blue: 1, alpha: 0.5), forState: .Highlighted)
addButton.titleLabel?.textAlignment = .Center
addButton.layer.zPosition = 3
addButton.addTarget(self, action: #selector(PlayersViewController.addPlayer(_:)), forControlEvents: .TouchUpInside)
enterPlayerPart.addSubview(addButton)
// MARK: TEXT FIELD
let textFieldSize = CGSize(width: enterPlayerPartSize.width - playerIconSize.width - addButtonSize.width, height: enterPlayerPartSize.height)
textField = UITextField(frame: CGRect(x: playerIconSize.width, y: 0, width: textFieldSize.width, height: textFieldSize.height))
textField.font = UIFont(name: "Avenir-Light", size: textFieldSize.height / 2)
textField.attributedPlaceholder = NSAttributedString(string:"Name", attributes:[NSForegroundColorAttributeName: UIColor(red: 1, green: 1, blue: 1, alpha: 0.5)])
textField.textColor = UIColor.whiteColor()
textField.textAlignment = NSTextAlignment.Left
textField.returnKeyType = UIReturnKeyType.Done
textField.delegate = self
enterPlayerPart.addSubview(textField)
Functions :
// MARK: ADD PLAYER - FUNCTION
func addPlayer(sender: UIButton) {
if textField.text == nil || textField.text == "" {
print("Enter player name")
} else {
createNewPlayer(textField.text!)
}
}
// MARK: CREATE NEW PLAYER - FUNCTION
func createNewPlayer(playerName: String) {
let playerPartSize = CGSize(width: screenWidth, height: screenHeight * 0.1)
let playerPart = UIView(frame: CGRect(x: 0, y: playerPartSize.height * CGFloat(playersCount), width: playerPartSize.width, height: playerPartSize.height))
scrollView.addSubview(playerPart)
// PLAYER ICON
let playerIconSize = CGSize(width: playerPartSize.height, height: playerPartSize.height)
let playerIcon = UILabel(frame: CGRect(x: 0, y: 0, width: playerIconSize.width, height: playerIconSize.height))
playerIcon.font = UIFont.fontAwesomeOfSize(topPartSize.height / 2)
playerIcon.text = String.fontAwesomeIconWithName(.User)
playerIcon.textAlignment = .Center
playerIcon.textColor = UIColor.whiteColor()
playerPart.addSubview(playerIcon)
// REMOVE BUTTON
let removeButtonSize = playerIconSize
let removeButton = UIButton(frame: CGRect(x: playerPartSize.width - removeButtonSize.width, y: 0, width: playerIconSize.width, height: playerIconSize.height))
removeButton.titleLabel!.font = UIFont.fontAwesomeOfSize(removeButtonSize.height / 2)
removeButton.setTitle(String.fontAwesomeIconWithName(.Trash), forState: .Normal)
removeButton.setTitleColor(UIColor.whiteColor(), forState: .Normal)
removeButton.setTitleColor(UIColor(red: 1, green: 1, blue: 1, alpha: 0.5), forState: .Highlighted)
removeButton.titleLabel?.textAlignment = .Center
removeButton.addTarget(self, action: #selector(PlayersViewController.removePlayer(_:)), forControlEvents: .TouchUpInside)
playerPart.addSubview(removeButton)
// PLAYER NAME
let playerNameSize = CGSize(width: playerPartSize.width - playerIconSize.width - removeButtonSize.width, height: playerPartSize.height)
let playerNameLabel = UILabel(frame: CGRect(x: playerIconSize.width, y: 0, width: playerNameSize.width, height: playerNameSize.height))
playerNameLabel.font = UIFont(name: "Avenir-Light", size: playerNameSize.height / 2)
playerNameLabel.text = playerName
playerNameLabel.textAlignment = .Left
playerNameLabel.textColor = UIColor.whiteColor()
playerPart.addSubview(playerNameLabel)
// BOTTOM LINE
let bottomLineHeight = playerPartSize.height / 50
let bottomLine = UIView(frame: CGRect(x: 0, y: playerPartSize.height - bottomLineHeight, width: playerPartSize.width, height: bottomLineHeight))
bottomLine.backgroundColor = UIColor(hexColor: 0x1B1B1B)
playerPart.addSubview(bottomLine)
// PLAYERS COUNT ++
playersCount += 1
}
Thanks for your help !
You have two ways to perform it :
You seem using reusable components, so you should have a look on UITableView / UICollectionView. It will manage memory usage, and components layout
If you dont want to migrate to theses components, it recommend you to store all your player views in an array, and relayout them after an add/remove operation
something like :
var players : [UIView] = []
func createNewPlayer(playerName: String) {
//Do your stuff here
players.append(newCreatedPalyerView)
layout()
}
func layout() {
var yPosition = 0
for view in players {
view.frame.origin.y = yPosition
yPosition += view.frame.size.height
}
}
func removePlayer(player : UIView) {
self.players.remove(player)
self.layout()
}
You need to manage your scenario something like below :
First take all the UI of one player in one UIView. So you should have all your one player's stuff(playername label, remove button etc) in one view. (i think you have it in playerPart - that`s cool)
Now you are adding that playerPart directly in scrollview i think, If it is like that then you should not do like this. You should have one UIView of same size of scrollview in scrollview(same as content size of scrollview) and you should add playerPart to that view.
Now you can get all subviews of that view in array something like :
NSArray *viewArr = [tempView subviews];
Assume tempView is vied over scrollview as i have mentioned above,
you can get subview of scrollview also by calling same method.
Now for example you have four player(four view) in view (temp view or scrollview if tempview is not added on it) and you click remove on player 3 then you can get index of that object from that view array. for example,
you have array of total view which you have get from
NSArray *viewArr = [tempView subviews];
then you can get index of player3 i.e view3 like,
int index = [viewArr indexOfObject:player3];
now make one method or function which is called after every removed call and pass that index as parameter in that method.
In that method take one for loop, which start from this index and run till last object (view) of this viewArr. Decrease y of every coming view in this for loop so your all view which was belowed removed view will goes up.
Here is one my method i was using in my one project to remove view and manage other accordingly :
#pragma mark UpdateFrameOfViews
-(void)updateFrameOfViewWithTag : (int) myTag increament : (CGFloat)increament fromIndex : (int)index{
CustomSubClassView *tempView = [self.view viewWithTag:myTag];
UIScrollView *tempScroll = (UIScrollView*)[tempView superview];
NSArray *subViews = [tempView subviews];
BOOL status = NO;
for (int i = index; i <= subViews.count; i++) {
if (i < subViews.count) {
UIView *tempView1 = [subViews objectAtIndex:i];
CGFloat newY = tempView1.frame.origin.y + increament;
CGRect newFrame = CGRectMake(tempView1.frame.origin.x, newY, tempView1.frame.size.width, tempView1.frame.size.height);
tempView1.frame = newFrame;
status = YES;
}
if (index == subViews.count && i == subViews.count) {
// UIView *tempView1 = [subViews lastObject];
//
// CGFloat newY = tempView1.frame.origin.y + increament;
//
// CGRect newFrame = CGRectMake(tempView1.frame.origin.x, newY, tempView1.frame.size.width, tempView1.frame.size.height);
//
// tempView1.frame = newFrame;
status = YES;
}
// tempView1.backgroundColor = [UIColor orangeColor];
}
if (status) {
CGRect containerFrame = CGRectMake(0, 0, self.view.frame.size.width, tempView.frame.size.height+increament);
tempView.frame = containerFrame;
tempScroll.contentSize = CGSizeMake(tempView.frame.size.width, tempView.frame.size.height);
}
}
This method is for reference just, because here scenario is little bit different.
My code is in objective and is for reference only convert in swift required!!
This was how you manage it!!
Suggestion : it is better to use UITableView in your case!!!
Hope this will help :)

Resources