This is cute ... I've copied the code from Apple's docs -- adding the lines about isUserInteractionEnabled for good measure -- but my MPVolumeView slider is totally unresponsive when I try and drag it. It does move appropriately when I click the volume button.
myVolumeViewParentView.backgroundColor = UIColor.clear
let myVolumeView = MPVolumeView(frame: myVolumeViewParentView.bounds)
myVolumeViewParentView.isUserInteractionEnabled = true
myVolumeView.isUserInteractionEnabled = true
myVolumeViewParentView.addSubview(myVolumeView)
When I look in the view debugger, I don't see any other view in front of it.
Any ideas? I saw a posting about this a couple of years ago, but there was no answer. Here's hoping the second time's the charm ...
I had the same issue. What solved it for me was to give the volume view a height constraint if you don't explicitly set it's frame.
To test this do:
myVolumeView.clipsToBounds = true
If the volume view doesn't show than you know there's no frame. You than need to set it's frame or give it a height constraint.
Related
I have a PDF that has pages of different heights that I want to display in a horizontally paged fashion, one page at a time, with no gaps between pages, and the top of each page being aligned with the top of the view. For example:
I would like to use PDFKit's PDFView, especially since these settings can be achieved by:
let pdfView = PDFView()
pdfView.displayDirection = .horizontal
pdfView.displaysPageBreaks = false
pdfView.usePageViewController(true, withViewOptions: nil)
However, this produces the following:
This is due to the varying heights of the pages. Each page is centered vertically in the PDFView, and if its height of the page is greater than the height of the view (e.g. Page 2), it is aspect-scaled to fit.
To attempt to solve the first issue, the vertical offset, I have attempted several things. First, and this is gross and I know it's wrong, after inspecting the view hierarchy and finding the private view (a view of class named PDFTextInputView) that the page is rendered within I've tried adjusting its frame's origin.y to 0 whenever the outer-most scrollview (another private view) scrolls. This works in that whenever the next or previous page is scrolling into view, the page is rendered at the top of the view. However, once the user pinches to zoom on that page, its frame is automatically jumps back to being centered. This approach, aside from relying on private view hierarchy, just won't work.
To attempt to solve the "tall page problem" (Page 2) I've tried adjusting the scale factor of the PDFView whenever a new page appears. Sadly, the .PDFViewPageChanged notification only fires after the scroll view ends decelerating, so the page is zoomed out as it's scrolling in and then when I adjust the scale factor it jumps to fit the width of the screen. Then I turned to the .PDFViewVisiblePagesChanged which does fire as the next/previous pages are coming into view (as well as a few more times, which is fine) and this has the same issue as with .PDFViewPageChanged.
Other things I've tried:
Adjusting the transform on the document view
Intercepting the pinch gesture and adjusting the document view's center
Creating my own page controller and render a single PDF page per page (not using pdfView.usePageViewController)
Does anyone have any ideas of how I can achieve what I'm after?
New Hack:
So it turns out, after digging through some more private APIs (yes, this solution is still gross) that there is this magical property that I had completely overlooked on PDFScrollView (a private, internal view) called... 🥁
🎉 forcesTopAlignment 🎉
To enable this, find the PDFScrollView in your PDFView and:
pdfScrollView.setValue(true, forKey: "forcesTopAlignment")
That does the trick!
Old Hack:
After a few days up against the wall I finally figured out a way to get PDFView to behave how I want.
Disclosure: This solution uses method swizzling and relies on private view hierarchy and could break at any time. That said, it works for now and I'm happy with the solution.
The full solution is available in this gist. (The meat is in overrideCenterAlign.)
There is a private method aptly named _centerAlign which vertically centers and scales the pdf pages as they come onto the screen and as they're scaled with the pinch gesture. Swizzling that method allows me to inject my own logic and apply my own transforms to position the pdf view how I'd like (with the top of the page aligned to the top of the view.)
There are two cases to consider. The "short page" case (pages 1, 3, 4 in the example) and the "tall page" case (page 2 in the example.) In both cases I start by invoking the original implementation of _centerAlign so that it can apply its transforms and manage updating the internal scroll view.
For the "short page" case, I apply the same transform with a vertical translation to align the top of the pdf with the top of the view.
For the "long page" case, I adjust the internal scroll view's zoom scale as it comes onto the screen so that it's scaled to fit the width of the view.
All of that said, I'm open to cleaner solutions that don't rely on private methods and view hierarchy. If you know of a better way to accomplish this, please post an answer!
Just simply set forcesTopAlignment to true
Example:
lazy var pdfView: PDFView = {
let pdf = PDFView()
pdf.displayMode = .singlePageContinuous
pdf.autoScales = true
pdf.translatesAutoresizingMaskIntoConstraints = false
pdf.setValue(true, forKey: "forcesTopAlignment")
return pdf
}()
How my answer is different from accepted one?
There is no property as a scrollView on pdfView available. So you just set value directly to pdfView.
NOTE: this answer is intended to HELP other guys arriving here trying to fix similar problems.
I discovered a nasty bug in iOS12 since apple introduced in iOS13 "drag to
hide" (Apple "https://developer.apple.com/design/human-interface-guidelines/ios/app-architecture/modality/").
Everything works fine if target is iOS13 or above.
In iOS12, maybe due to a different calculated size, (I guess..) pdf is rendered wrong: some contents go to right and is clipped away.
A dirty fix consists in don't calling
self?.pdfView.autoScales = true
in viewDidLoad,
but after loading every PDF waiting some mS AND set it:
if #available(iOS 13.0, *) {
// NADA.. go on..
}else{
self.pdfView.autoScales = false // disable it.
self?.pdfView.alpha = 1
}
let pdf = load .......
self.pdfView.document = pdf
if #available(iOS 13.0, *) {
//nada..
} else {
// hack for ios 12
let when = DispatchTime.now() + 0.25
DispatchQueue.main.asyncAfter(deadline: when, execute: { [weak self] in
self?.pdfView.autoScales = true // hack for ios 12
self?.pdfView.alpha = 1
})
}
Use alpha to prevent viewing pdf not adopted yet.
I think the problem is with adding PDFView to view with constraint. When I added using just addSubview() and user proper frame, it does not require to call setValue.
let pdfView = PDFKit.PDFView(frame: self.view.bounds)
pdfView.document = PDFKit.PDFDocument(url: url)
pdfView.autoresizingMask = [.flexibleHeight, .flexibleWidth]
view.addSubview(pdfView)
I'm encountering a strange issue with VoiceOver.
Goals:
Set a UIStackView containing multiple UILabel's as my navigationItem.titleView.
Mark the stack view as an accessibility element and set its accessibilityLabel to an appropriate value.
Set the stack view as the initial VoiceOver focus by calling UIAccessibility.post(notification: .screenChanged, argument: navigationItem.titleView) inside viewDidAppear(animated:).
Expected Result:
When the view controller appears the focus appears to be on the title view and VoiceOver reads the contents of the accessibility label one time.
Actual Result:
VoiceOver starts to read the contents of the accessibility label and then part way through (or sometimes after finishing) it proceeds to read it a second time.
This issue does not occur if I set navigationItem.titleView to an instance of UILabel.
Does anyone know why this happens? Is it a bug in iOS?
I have set up a simple project demonstrating the issue here:
https://github.com/rzulkoski/Focus-TitleView-Bug
The reason why you have a second time reading of your title is in your code.
In your viewDidLoad, you set the stackview accessibility label that VoiceOver automatically reads out to inform the user of the changing.
Next, you notify this changing with a post in your viewDidAppear that VoiceOver naturally reads out as well.
To prevent from this behavior, just delete stackView.accessibilityLabel = label.text in your setupNavigationItem function and add this snippet in your private lazy var label init :
if (self.view.subviews.contains(stackView)) {
stackView.accessibilityLabel = label.text
}
Updating the stackView.accessibilityLabel this way doesn't trigger VoiceOver to inform the user and allows to get your purpose.
However, I don't recommend to read out the title as the first element of a new page unless you reorder the presented elements.
VoiceOver users won't naturally guess that another element is present before the title :
They may not find a way to get back to the previous page.
They may be lost if they get the first element of the page with a 4 fingers simple-tap because they'll get the back button and not the title.
Technically, your problem is solved with the piece of code above but, conceptually, I suggest to reorder your elements if you still want to expose the title as the first element.
==========
EDIT (workaround)
About the technical problem, you're right in your comment, this solution above works thanks to the label reading by VoiceOver.
I commited a solution in your git branch you gave in your initial post.
The problem deals with the UIStackView I cannot explain in this case and cannot solve neither as is.
To reach your purpose, I created a UIAccessibilityELement for the stackview that can be perfectly reached and exposed with no double reading with a postnotification.
I did that because I couldn't get programmatically the stackview new size when the labels are in... maybe creating a UIStackView subclass and get into its layoutSubviews could be the trick ?
This solution should work as a workaround but I don't know the reason why this behavior appears with a UIStackview.
==========
EDIT (solution)
The problem is the way the titleView of the navigationItem is created. The best way to achieve your purpose is to :
Initialize your titleView as a simple UIView whose frame is the same as the stackview's.
Add the stackview as a subview after having specified its frame and its accessibility properties.
Follow the steps hereafter in your code :
Add the .header trait in the stackview property :
private lazy var stackView: UIStackView = {
let stackView = UIStackView(frame: .zero)
stackView.axis = .vertical
stackView.alignment = .center
stackView.distribution = .equalSpacing
stackView.isAccessibilityElement = true
stackView.accessibilityTraits = .header
return stackView
}()
Change the stackview case in your 'switch...case...' code section as below :
case .stackView:
label.text = "UIStackView"
label.sizeToFit()
stackView.addArrangedSubview(label)
label2.text = subtitle
label2.sizeToFit()
stackView.addArrangedSubview(label2)
stackView.frame.size.width = max(label.frame.width, label2.frame.width)
stackView.frame.size.height = label.frame.height + label2.frame.height
stackView.accessibilityLabel = label.text?.appending(", \(label2.text!)")
navigationItem.titleView = UIView(frame: stackView.frame)
navigationItem.titleView?.addSubview(stackView)
}
Now, the postNotification reads out your stackview only once as the first element of your screen.
I have two UIButtons and a strange rectangle appears when tapping one of them. I don't know why. I set the images as background images on the button and it worked fine until now.
What I changed now is that I set each button isSelected property and before I did not
Like this:
thumbsDownButton.isSelected = true
thumbsUpButton.isSelected = false
Obviously what I want is for that rectangle to go away
The style was set to System. Setting it to Custom fixed the problem and the rectangle is not appearing anymore. I set it from the xib (if it makes any difference)
Add this Code
thumbsDownButton.tintColor = UIColor.white
It seems your frame may be calculated in the wrong way.
Try to use this one to check it.
button.clipToBounds = true
Please share more code related of initializing and setting up the frame of your view.
i have a game, which update method is called every 0.5 seconds. and i have a second method which is called every 2 seconds and should change the text in my Labels:
func changeName(){
textLabel.text = "this is a test, 4 U <3"
textLabel.hidden = false
}
but then all my other views (in VC) are reseting to their original position. i don't know why!
Can anyone please help me??
i tried it with UIViews and it worked perfect, with hiding. But there seems to be a prob with the name changing. Is there a solution?
If what you are saying that the width of your UILabel changes with the text, then I suggest you add constraint on the UILabel's width. If you are using storyboard select the UILabel in the SB and click the lower right icon like in this picture and check width:
I am having trouble with a UIScrollView... I have researched a lot and found cases that were similar yet the solutions never seemed to work so here's a little explanation (it's quite hard to explain)
I have a viewcontroller that contains a UIScrollView and I load a couple of view controllers inside that uiscrollview... I instatnite them and then I add them to the scrollView like this...
viewController1.view.frame = scrollView1.bounds
var frame0 = viewController1.view.frame
frame0.origin.x = 0
viewController1.view.frame = frame0
self.addChildViewController(viewController1)
self.scrollView1.addSubview(viewController1.view)
viewController1.didMoveToParentViewController(self)
viewController2.view.frame = scrollView1.bounds
var frame01 = viewController2.view.frame
frame01.origin.x = self.view.frame.size.width
viewController2.view.frame = frame01
self.addChildViewController(viewController2)
self.scrollView1.addSubview(viewController2.view)
viewController2.didMoveToParentViewController(self)
...
after that I configure the scroll view as follows
self.scrollView1.contentSize = CGSizeMake(self.view.frame.width * 5, self.view.frame.height)
scrollView1.delaysContentTouches = false
Times 5 because there are 5 ViewControllers
(The scroll view has paging enabled)
Now this method works quite well and all views are loaded inside the scroll view and I can swipe trough my views however here's the problem.
My first view is loaded fine, then when I swipe to the right I see that the second view is offset to the top and a part of it is hidden, then when I touch a button inside the view it suddenly snaps to the bottom in it's place... This is really annoying because it makes the user interface really irritating since a part of it is hidden...
I have made an illustration of it: http://imgur.com/xHRwXt4
I have tried a bunch of things like setting
self.automaticallyAdjustsScrollViewInsets = false
or
self.scrollView1.clipsToBounds = true
but still the same problem occurs. I also thaught it was my navigation bar since the view containing the scrollview is located inside a ViewController that has a navigation bar (from uinavigationcontroller)
So for testing purposes I tried it without a navigation controller and bar but still the same kind of problem occurred. It's really frustrating and I can't seem to find a solution.
However I do have to say when the scrollview was located inside the ViewController with navigationBar and I set
self.scrollView1.contentSize = CGSizeMake(self.view.frame.width * 5, self.view.frame.height - 66)
(Notice the -66)
Then the snapping problem didn't occur, however this wasn't the effect I was looking for since the scrollview needed to fill up the whole view, also the view inside the UIScrollView wasn't completely visible then so that wasn't a solution either.
I also taught it was maybe because my constraints were differently set up on one of the view controllers I loaded so I tried it with initiating the same type of view controller a few times still the same problem occurred...
(So I have tried a lot but no matter what I try to do it gets worse or another problem occurs for each view moves in either direction then I add self.automaticallyAdjustsScrollViewInsets = false but then the snapping returns etc. etc.)
Hopefully somebody has some suggestions to solve this
(If I you need some more illustrations etc. then I will provide these too.)
Thank You
From the looks of it, there are 2 things to try:
Some of child view controllers may contain scroll views and their automaticallyAdjustsScrollViewInsets = false needs to be set other than the parent view controller
A very helpful way to find out is to turn on view debugging. Run the project, then go to Xcode > View Debugging > Show View Frames. See if the child views of the child view controllers are misaligned along the top. If they are properly aligned (top all flushed), it means the problem lies in the individual child views.