func addStartingButtons2() {
let button = UIButton(frame: CGRect(x: 50, y: buttonY, width: 250, height: 30))
button.layer.cornerRadius = 10 // get some fancy pantsy rounding
button.backgroundColor = UIColor.darkGray
button.setTitle("Button for Villain: \(String(describing: hashes[button]))", for: UIControlState.normal)
buttons.append(button)
buttons.append(button)
for button in buttons {
self.view.addSubview(button)
button.frame.origin.y += 50
}
}
Where:
var hashes = [UIButton : [Double]]()
var buttons = [UIButton]()
Want to add this function 2 same buttons one onder other, but only one button is realised
Well yeah, you are trying to add the same button twice.
I didn't test the code but you should be able to do this by putting the logic in a for loop and adding a bit of padding between the buttons, otherwise you will see them overlapping.
Something like this:
func addStartingButtons2() {
for villain in villains { // this depends on your needs, it's just an example
let button = UIButton(frame: CGRect(x: 50, y: buttonY, width: 250, height: 30))
// Add offset for the next button.
buttonY += 40
button.layer.cornerRadius = 10 // get some fancy pantsy rounding
button.backgroundColor = UIColor.darkGray
button.setTitle("Button for Villain: \(String(describing: hashes[button]))", for: UIControlState.normal)
buttons.append(button)
}
for button in buttons {
self.view.addSubview(button)
button.frame.origin.y += 50
}
// If you want to avoid the second loop
// you can just put view.addSubview(button) inside the first for loop
}
This won't work. Your array buttons is storing a reference of your button, so you are actually putting two references into the array.
To see the result by yourself, put a break point in your loop and you will see the button with same address twice.
If you want to have multiple buttons, you have to create them one by one.
Related
I have implemented this Popover Cocoapod by Corin8823,
My Popover code mirrors this code but have a situation to where the origin point of where of the pop over is coming from is my tab bar instead of the button itself.
I have my popover button within a PageViewController linked to a UIView.
I have recreated that set up (minus the Popover Cocoapod) here but you can also see a youtube video approximating that set up here.
I have tried changing the sender to other elements and simply removing it but the results are always the same (popover comes from tab bar). I assume the popover is coming from the parent aka the UIView? instead of the child? I have read in some posts (that I've been unable to relocate) that I should be using something other than sender.frame, but I don't know what to use instead. I also don't see any questions similar enough to this situation that aren't extremely outdated.
The exact Popover code within the button is:
#IBAction func button1Action(_ sender: UIButton) {
//Origin Point
let originPoint = CGPoint(x: sender.frame.midX, y: sender.frame.maxY + 158) //(Y = down)
let popoverView = UIView(frame: CGRect(x: 32, y: sender.bounds.maxY + 20, width: self.view.bounds.width - 64, height: 200))
let textLabel = UILabel(frame: CGRect(x: +15, y: 0, width: popoverView.bounds.width - 15, height: popoverView.bounds.height))
textLabel.lineBreakMode = .byWordWrapping
textLabel.numberOfLines = 0
textLabel.textColor = .white
textLabel.text = "Use neutral framing of the events rather than perceptually colored language. eg:\n\n____ event occured.\n____ person said: '____'.\n____ object was altered in ____ way."
textLabel.textAlignment = .left
popoverView.addSubview(textLabel)
let options: [PopoverOption] = [
.cornerRadius(25),
.color(.systemTeal)
]
let popover = Popover(options: options)
popover.show(popoverView, point: originPoint)
}
I have posted this on the github and it's been about 3 weeks with no response.
I'm writing an iOS app where the user can add text fields, then drag them around the screen to reposition them, layout-style, sort of like Keynote.
I'm currently appending the user-added UITextFields to an #IBOutlet Collection and defaulting to .borderStyle = .roundedRect to get a faint border around the selected text, indicating the field is selected. Any UITextField will be set to .roundedRect border style when textFieldDidBeginEditing is called, and switch to textField.borderStyle = .none when textFieldDidEndEditing is called.
All seems to work with one problem: when switching border style to .none, the text field loses indentation that was around the border, shifting text outward and putting it in a spot where the user hadn't intended (graphic adds a background color red, just to show the shift, but I'll eventually allow the user to set background colors, so just shifting the UITextField isn't an option).
I've also tried adapting the answer at:
Create space at the beginning of a UITextField
setting a no-padding inset for the TextView when it's a .roundedRect, but adding padding when .borderStyle is .none. This seems to have no effect.
Other answers have suggested setting
textField.layer.borderColor = UIColor.clear.cgColor
or
textField.layer.borderWidth = 0.0
but these don't seem to have any effect, either
I'm eventually going to allow the user to change fonts & sizes of each TextField, so I'd like any indentation to be consistent whether the UITextField is selected or nots elected, and regardless of font choices.
Code is below. Recommendations are most welcome, as well as setting me on a new approach, if I'm missing a better solution.
Thanks!
John
class ViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var screenView: UIView! // a 320 x 240 view
#IBOutlet var fieldCollection: [UITextField]! // Not connected, fields created programmatically
// below are used in .inset(by:) but seems to have no effect
let padding = UIEdgeInsets(top: 0, left: 5, bottom: 0, right: 5)
let noPadding = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
override func viewDidLoad() {
super.viewDidLoad()
// hide keyboard if we tap outside of a field
let tap = UITapGestureRecognizer(target: self.view, action: #selector(UIView.endEditing(_:)))
tap.cancelsTouchesInView = false
self.view.addGestureRecognizer(tap)
createNewField()
}
// Select / deselect text fields
func textFieldDidBeginEditing(_ textField: UITextField) {
textField.borderStyle = .roundedRect
// textField.bounds.inset(by: noPadding) // effect is the same if left out
}
func textFieldDidEndEditing(_ textField: UITextField) {
textField.borderStyle = .none
// textField.bounds.inset(by: padding) // effect is the same if left out
}
// UITextField created & added to fieldCollection
func createNewField() {
let newFieldRect = CGRect(x: 0, y: 0, width: 320, height: 30)
let newField = UITextField(frame: newFieldRect)
newField.borderStyle = .roundedRect
newField.isUserInteractionEnabled = true
newField.addGestureRecognizer(addGestureToField())
screenView.addSubview(newField)
if fieldCollection == nil {
fieldCollection = [newField]
} else {
fieldCollection.append(newField)
}
newField.delegate = self
newField.becomeFirstResponder()
}
func addGestureToField() -> UIPanGestureRecognizer {
var panGesture = UIPanGestureRecognizer()
panGesture = UIPanGestureRecognizer(target: self, action: #selector(draggedView(_:)))
return panGesture
}
// event handler when a field(view) is dragged
#objc func draggedView(_ sender:UIPanGestureRecognizer){
sender.view!.becomeFirstResponder()
let selectedView = sender.view as! UITextField
selectedView.bringSubviewToFront(selectedView)
let translation = sender.translation(in: screenView)
selectedView.center = CGPoint(x: selectedView.center.x + translation.x, y: selectedView.center.y + translation.y)
sender.setTranslation(CGPoint.zero, in: screenView)
}
#IBAction func addFieldPressed(_ sender: UIButton) {
createNewField()
}
}
I was able to work around the problem by subclassing UITextField:
class PaddedTextField: UITextField {
let padding = UIEdgeInsets(top: 0, left: 8, bottom: 0, right: 8)
let noPadding = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
override open func textRect(forBounds bounds: CGRect) -> CGRect {
if self.borderStyle == .none {
let content = bounds.inset(by: padding)
return content
} else {
return bounds.inset(by: noPadding)
}
}
}
I then changed the newField object creation from using UITextField to:
let newField = PaddedTextField(frame: newFieldRect)
One more change. The height needed to be more appropriately calculated. Since all of my text fields can start out the full length of the enclosing superview (320 points), I modified the original newFieldRect, used .sizeToFit() to create a textbox with the appropriate height. The other dimensions won't be correct b/c I don't have anything in the text view, but I extract the .height and reuse this with my original initliazation parameters.
newField.sizeToFit()
let newFieldHeight = newField.frame.height
newFieldRect = CGRect(x: 0, y: 0, width: 320, height: newFieldHeight)
newField.frame = newFieldRect
Here's hoping it helps save someone time.
I'm new to navigation transition. I'm using Swift language for development.
What i'm trying to do is, whenever user click on the tableview cell the new view will open and it look like it coming from the image view of the cell.
I created a class of UIViewControllerAnimatedTransitioning.
class ThumbnailZoomTransitionAnimator: NSObject, UIViewControllerAnimatedTransitioning {
private let duration: TimeInterval = 0.5
var operation: UINavigationControllerOperation = .push
var thumbnailFrame = CGRect.zero
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return duration
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let presenting = operation == .push
// Determine which is the master view and which is the detail view that we're navigating to and from. The container view will house the views for transition animation.
let containerView = transitionContext.containerView
guard let toView = transitionContext.view(forKey: UITransitionContextViewKey.to) else { return }
guard let fromView = transitionContext.view(forKey: UITransitionContextViewKey.from) else { return }
let storyFeedView = presenting ? fromView : toView
let storyDetailView = presenting ? toView : fromView
// Determine the starting frame of the detail view for the animation. When we're presenting, the detail view will grow out of the thumbnail frame. When we're dismissing, the detail view will shrink back into that same thumbnail frame.
var initialFrame = presenting ? thumbnailFrame : storyDetailView.frame
let finalFrame = presenting ? storyDetailView.frame : thumbnailFrame
// Resize the detail view to fit within the thumbnail's frame at the beginning of the push animation and at the end of the pop animation while maintaining it's inherent aspect ratio.
let initialFrameAspectRatio = initialFrame.width / initialFrame.height
let storyDetailAspectRatio = storyDetailView.frame.width / storyDetailView.frame.height
if initialFrameAspectRatio > storyDetailAspectRatio {
initialFrame.size = CGSize(width: initialFrame.height * storyDetailAspectRatio, height: initialFrame.height)
}
else {
initialFrame.size = CGSize(width: initialFrame.width, height: initialFrame.width / storyDetailAspectRatio)
}
let finalFrameAspectRatio = finalFrame.width / finalFrame.height
var resizedFinalFrame = finalFrame
if finalFrameAspectRatio > storyDetailAspectRatio {
resizedFinalFrame.size = CGSize(width: finalFrame.height * storyDetailAspectRatio, height: finalFrame.height)
}
else {
resizedFinalFrame.size = CGSize(width: finalFrame.width, height: finalFrame.width / storyDetailAspectRatio)
}
// Determine how much the detail view needs to grow or shrink.
let scaleFactor = resizedFinalFrame.width / initialFrame.width
let growScaleFactor = presenting ? scaleFactor: 1/scaleFactor
let shrinkScaleFactor = 1/growScaleFactor
if presenting {
// Shrink the detail view for the initial frame. The detail view will be scaled to CGAffineTransformIdentity below.
storyDetailView.transform = CGAffineTransform(scaleX: shrinkScaleFactor, y: shrinkScaleFactor)
storyDetailView.center = CGPoint(x: thumbnailFrame.midX, y: thumbnailFrame.midY)
storyDetailView.clipsToBounds = true
}
// Set the initial state of the alpha for the master and detail views so that we can fade them in and out during the animation.
storyDetailView.alpha = presenting ? 0 : 1
storyFeedView.alpha = presenting ? 1 : 0
// Add the view that we're transitioning to to the container view that houses the animation.
containerView.addSubview(toView)
containerView.bringSubview(toFront: storyDetailView)
// Animate the transition.
UIView.animate(withDuration: duration, delay: 0.0, usingSpringWithDamping: 1, initialSpringVelocity: 1.0, options: .curveEaseInOut, animations: {
// Fade the master and detail views in and out.
storyDetailView.alpha = presenting ? 1 : 0
storyFeedView.alpha = presenting ? 0 : 1
if presenting {
// Scale the master view in parallel with the detail view (which will grow to its inherent size). The translation gives the appearance that the anchor point for the zoom is the center of the thumbnail frame.
let scale = CGAffineTransform(scaleX: growScaleFactor, y: growScaleFactor)
let translate = CGAffineTransform(translationX: storyFeedView.frame.midX - self.thumbnailFrame.midX, y: storyFeedView.frame.midY - self.thumbnailFrame.midY)
storyFeedView.transform = translate.concatenating(scale)
storyDetailView.transform = .identity
}
else {
// Return the master view to its inherent size and position and shrink the detail view.
storyFeedView.transform = .identity
storyDetailView.transform = CGAffineTransform(scaleX: shrinkScaleFactor, y: shrinkScaleFactor)
}
// Move the detail view to the final frame position.
storyDetailView.center = CGPoint(x: finalFrame.midX, y: finalFrame.midY)
}) { finished in
transitionContext.completeTransition(finished)
}
}
}
When i click on the cell then new controller open. This functionality is working correctly. But whenever i'm going back then my previous controller get disappear.
Is i'm doing something wrong here ,
// Animate the transition.
UIView.animate(withDuration: duration, delay: 0.0, usingSpringWithDamping: 1, initialSpringVelocity: 1.0, options: .curveEaseInOut, animations: {
// Fade the master and detail views in and out.
storyDetailView.alpha = presenting ? 1 : 0
storyFeedView.alpha = presenting ? 0 : 1
if presenting {
// Scale the master view in parallel with the detail view (which will grow to its inherent size). The translation gives the appearance that the anchor point for the zoom is the center of the thumbnail frame.
let scale = CGAffineTransform(scaleX: growScaleFactor, y: growScaleFactor)
let translate = CGAffineTransform(translationX: storyFeedView.frame.midX - self.thumbnailFrame.midX, y: storyFeedView.frame.midY - self.thumbnailFrame.midY)
storyFeedView.transform = translate.concatenating(scale)
storyDetailView.transform = .identity
}
else {
// Return the master view to its inherent size and position and shrink the detail view.
storyFeedView.transform = .identity
storyDetailView.transform = CGAffineTransform(scaleX: shrinkScaleFactor, y: shrinkScaleFactor)
}
// Move the detail view to the final frame position.
storyDetailView.center = CGPoint(x: finalFrame.midX, y: finalFrame.midY)
}) { finished in
transitionContext.completeTransition(finished)
}
I'm trying to Scale the master view in parallel with the detail view (which will grow to its inherent size). The translation gives the appearance that the anchor point for the zoom is the center of the thumbnail frame.
It will give zoomIn ZoomOut effect when user go back to previous controller. But When i trying to do this then my previous controller disappear.
I wanted to do like zoom transition like apple do in their apps.
I'm developing image gallery like slider using UIPageViewController and I'm having troubles with UIPageViewController automatic insets in Scroll transition style mode.
Here is my layout:
UIViewController with UIContainerView (magenta background)
UIPageViewController linked to the container (from #1)
List of dynamically created view UIViewController(s) within the page controller (from #2), full width-height views (1. orange, 2. red, 3. green)
It used to work fine for a long time and continue to work with iOS 11 unless it's rendered on iPhone X device with safe area:
I've checked a lot of various options and was able to confirm that it's related specifically to the Scroll mode of the Page Controller. If I switch to PageCurl transition style - it works as expected (full height):
The Page Controller doesn't expose a lot of options to control this behavior for the scroll mode and I wasn't able to "hack" it as well by searching the controls tree and modifying various insets and frame and contentSize related properties. What I can clearly see is that once view controller is created, my scroll view contentSize and frame is 34px smaller then the container frame
> view.frame
{{X=0,Y=0,Width=375,Height=732}}
Bottom: 732
Height: 732
IsEmpty: false
Left: 0
Location: {{X=0, Y=0}}
Right: 375
Size: {{Width=375, Height=732}}
Top: 0
Width: 375
X: 0
Y: 0
> scroll.frame
{{X=-5,Y=0,Width=385,Height=698}}
Bottom: 698
Height: 698
IsEmpty: false
Left: -5
Location: {{X=-5, Y=0}}
Right: 380
Size: {{Width=385, Height=698}}
Top: 0
Width: 385
X: -5
Y: 0
> scroll.contentSize
{{Width=1155, Height=698}}
Height: 698
IsEmpty: false
Width: 1155
I've also set up my autolayout constraints to be linked to superview rather than safe area:
Here is my code for the Home Controller and all the rest is set in a storyboard (alert: C# Xamarin syntax)
private List<UIViewController> viewControllers;
public HomePageViewController (IntPtr handle) : base ( handle)
{
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
var child1 = new UIViewController();
child1.View.BackgroundColor = UIColor.Orange;
var child2 = new UIViewController();
child2.View.BackgroundColor = UIColor.Red;
var child3 = new UIViewController();
child3.View.BackgroundColor = UIColor.Green;
this.viewControllers = new List<UIViewController>
{
child1,
child2,
child3,
};
this.SetViewControllers(new UIViewController[] { child1 }, UIPageViewControllerNavigationDirection.Forward, false, null);
this.GetNextViewController = (c, r) =>
{
var current = this.viewControllers.IndexOf(this.ViewControllers[0]);
if (current >= this.viewControllers.Count - 1)
return null;
return this.viewControllers[current + 1];
};
this.GetPreviousViewController = (c, r) =>
{
var current = this.viewControllers.IndexOf(this.ViewControllers[0]);
if (current <= 0)
return null;
return this.viewControllers[current - 1];
};
}
How can I force my children view controllers to have full height (equals to the frame height of the parent container)?
I think you can solve this issue using code and custom layout. I mean create your UIPageViewController and insert its view to your UIViewController in code not on storyboard. I think you
should override UIViewController.viewDidLayoutSubviews() and set your rects "manually" (at least the one of the UIPageViewController.) Well, when you do it in code, sometimes you even don't need to override UIViewController.viewDidLayoutSubviews() because the template by Apple itself didn't do this. I think because any created view has translatesAutoresizingMaskIntoConstraints = true. So you can also follow this approach.
There is an example when you create a new project and state it is a page based app.
Here is the template if you want (WARNING: This is a part of a template by Apple itself)
var pageViewController: UIPageViewController?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
// Configure the page view controller and add it as a child view controller.
self.pageViewController = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil)
self.pageViewController!.delegate = self
let startingViewController: DataViewController = self.modelController.viewControllerAtIndex(0, storyboard: self.storyboard!)!
let viewControllers = [startingViewController]
self.pageViewController!.setViewControllers(viewControllers, direction: .forward, animated: false, completion: {done in })
self.pageViewController!.dataSource = self.modelController
self.addChildViewController(self.pageViewController!)
self.view.addSubview(self.pageViewController!.view)
// Set the page view controller's bounds using an inset rect so that self's view is visible around the edges of the pages.
var pageViewRect = self.view.bounds
if UIDevice.current.userInterfaceIdiom == .pad {
pageViewRect = pageViewRect.insetBy(dx: 40.0, dy: 40.0)
}
self.pageViewController!.view.frame = pageViewRect
self.pageViewController!.didMove(toParentViewController: self)
}
You can extend this functionality by extending
I had a similar issue that only happened in the X sizes and after hours of trails and errors I got it fixed.
I am not sure if it's applicable for you or not but the way I have my page view controller VCs is that each VC has an image filling its background. I have 3 pages. Scrolling for the first time looked normal but when I would reverse scroll from page 2 to 1 or from 1 to 0, page 1's image would show around 40 pixels from the side when it should be completely hidden (similar to your screenshots).
So to fix it I had to either set the images to Aspect Fit or clips to bounds = true. I used the latter because it worked better for the UI.
I achieved to have my UIPageViewController display with full screen height with the following code :
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
guard let contentScrollView = view.subviews.first(where: { $0 is UIScrollView }) else { return }
contentScrollView.frame.size.height = view.frame.height
}
I'm trying to do a simple task of creating a activity indicator with a label that says, "loading" or "saving" or whatever I program it to say when it is running. I can not seem to figure out how to get it to be directly under my activity indicator though, right now it is right along the side of it and I want to to be centered below it.
Here is my code:
public func show(viewController : UIViewController) {
Async.main {
self.spinner = UIActivityIndicatorView(frame: CGRectMake(0, 0, self.size, self.size))
self.activityLabel = UILabel(frame: CGRectMake(0,0,200,200))
if let spinner = self.spinner {
spinner.activityIndicatorViewStyle = self.style
let screenSize: CGRect = UIScreen.mainScreen().bounds
spinner.center = CGPoint (x: screenSize.width/2 , y: screenSize.height/2)
spinner.hidesWhenStopped = true
viewController.view.addSubview(spinner)
spinner.startAnimating()
self.activityLabel?.center = CGPoint (x: screenSize.width/2 , y: screenSize.height/1.9 )
self.activityLabel?.text = self.textmessage
viewController.view.addSubview(self.activityLabel!)
}
}
}
Thanks for any help!
I think the problem you are having with the label being misaligned is the fact that you didn't set the label's textAlignment to .center. You need to add:
self.activityLabel.textAlignment = .center
That should put the text inside the label directly under the spinner.