In my app, users can select from a range of UI color-palettes to customize the look of the app to suite their preference.
The app's root viewcontroller is a UINavigationController (this app is not using SwiftUI, nor has it adopted UIScene yet).
However, I noticed only the first time (to be specific: this is in the appDelegate's application(:didFinishLaunchingWithOptions:), so before anything is actually rendered to the screen) the appearance settings are applied, but subsequent changes do not take effect.
However, the following code seems to 'fix' the issue:
self.window?.rootViewController = nil
self.updateAppearance()
self.window?.rootViewController = navigationController
This to me seems hacky/kludgy and unlikely to be a sustainable 'fix', and I think it indicates that the appearance information is only taken into account when a view(controller) gets inserted into the view(controller) hierarchy, in other words: when it gets drawn.
So then the question would be: how to tell a UINavigationController / Bar to redraw using the updated appearance settings?
I'm sure I'm overlooking something relatively simple (as I imagine this kind of thing is something many apps do to support darkmode), but alas, I can't seem to find it.
I setup a small test-project to isolate this issue. It has only one custom viewcontroller (ContentViewController) which only sets the backgroundColor in viewDidLoad(), and an AppDelegate (code below). No storyboards, no UIScene related things.
So, here is the minimal code that reproduces my issue:
AppDelegate.swift:
import UIKit
@main
class AppDelegate: UIResponder, UIApplicationDelegate
{
var window: UIWindow?
var rootNavigationController: UINavigationController?
let contentViewController = ViewController()
let timer = DispatchSource.makeTimerSource(queue: DispatchQueue.main)
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool
{
window = UIWindow()
let navigationController = UINavigationController(rootViewController: contentViewController)
rootNavigationController = navigationController
window?.rootViewController = navigationController
window?.makeKeyAndVisible()
// initial setup of appearance, this first call will take effect as expected
updateAppearance()
// setup a timer to repeatedly change the appearance of the navigationbar
timer.setEventHandler
{
/// ! uncommenting this line and the one after `self.updateAppearance()`
/// will cause `self.updateAppearance()` to take effect as expected
// self.window?.rootViewController = nil
self.updateAppearance()
//self.window?.rootViewController = navigationController
}
timer.schedule(deadline: .now(),
repeating: .seconds(2),
leeway: .milliseconds(50))
timer.resume()
return true
}
// some vars to automatically toggle between colors / textstyles
var colorIndex = 0
let colors = [ UIColor.blue, UIColor.yellow]
let textStyles: [UIFont.TextStyle] = [.title3, .body]
func updateAppearance()
{
let color = colors[colorIndex]
let textStyle = textStyles[colorIndex]
contentViewController.title = "bgColor: \(colorIndex) \(textStyle.rawValue)"
colorIndex = (colorIndex == 0) ? 1 : 0
let textColor = colors[colorIndex]
self.rootNavigationController?.navigationBar.isTranslucent = false
let navigationBarAppearance = UINavigationBarAppearance()
navigationBarAppearance.configureWithOpaqueBackground()
navigationBarAppearance.backgroundColor = color
let textAttributes: [NSAttributedString.Key : Any] =
[.font: UIFont.preferredFont(forTextStyle: textStyle),
.foregroundColor: textColor ]
navigationBarAppearance.titleTextAttributes = textAttributes
UINavigationBar.appearance().standardAppearance = navigationBarAppearance
UINavigationBar.appearance().compactAppearance = navigationBarAppearance
UINavigationBar.appearance().scrollEdgeAppearance = navigationBarAppearance
}
}
ViewController:
import UIKit
class ViewController: UIViewController
{
override func viewDidLoad()
{
super.viewDidLoad()
view.backgroundColor = UIColor.lightGray
}
}
Post not yet marked as solved
As per the title, but let me provide some more context:
on macos, using the Photos app, I can change the ‘poster frame, for a video. This frame is the one displayed as a thumbnail.
on iOS’ Photos app this functionality does not (see to?) exist.
So, to solve my own problem, I would like to build an app that does just that.
I am not sure how ‘poster frame’ is implemented, and have no idea where to start looking (perusing the PhotoKit and AVAssetWriter documentation, I didn’t find any hints on ‘poster frame’.
I am just looking for some pointers at this point, to understand if this is at all possible.
Post not yet marked as solved
Hey all,
I'm currently investigating whether to integrate an app with HealthKit.
I can't seem to find how a dev would go about testing that integration.
This app would only be writing data to HealthKit, not reading from it.
What is a good dev-strategy for working with HealthKit? "No need, non-production builds write into some kind of sandbox, does not affect 'real' HK data" ?
Use a separate device + iCloud account
Write some test/dev data, and delete
Any suggestion (or links to docs that I missed) are much appreciated!
Post not yet marked as solved
When using UIDocumentBrowserViewController in conjunction with a 'file' created by UIManagedDocument, the 'file' is seen by UIDocumentBrowserViewController as what it really is: a folder. Tapping on the folder shows a subfolder named 'StoreContent', which in turn shows the CoreData generated files ('persistentStore', 'persistenStore.shm' and 'persistentStore.wal'). These are grayed out, and also show the icon of another app.How would one go about using UIManagedDocument in conjunction with UIDocumentBrowserViewController?I filed a bug for this on July 30, but this one was ignored so far.
Post not yet marked as solved
I built a mac app, using old fashioned frameworks, cocoa.I'm running Xcode Version 11.2.1 (11B500), on macOS Mojave 10.14.6 (18G1012).My app runs in what appears to be non retina-resolution (labels, textfields, mapkit) when I launch the app from the /Applications folder.The build is obtained by choosing 'Archive', then 'Distribute App' and then 'Copy App'. The exported folder contains the app-bundle. When I launch this bundle from that folder, stored on ~/Desktop, it runs in seemingly retina resolution, but when I run it from /Applications, it runs in what appears non-retina resolution.The app has all Icons set, for all resolutions. What couls be causing this behaviour?
Post not yet marked as solved
Question:How can I make it so that the 'undo' menuItem actions reaches the NSApplication-subclass?Context:In my storyboard there is an NSMenuItem called 'Undo', it came with the template.My app uses a subclass of NSApplication, and in this class I implemented@objc func undo(_ sender: Any){}I did not implementoverride func validateMenuItem(_ menuItem: NSMenuItem) -> BoolIn the storyboard the menuItem's action is 'undo:', and the target is 'first responder'.The problem I have is that the menuItem is never enabled, and when I implement 'validateMenuItem(_:)', it is called only for 'startDictation:' and 'orderFrontCharacterPalette:'.However, when I change the method signature in my NSApplication subclass to:@objc@IBActionfunc undo2(_ sender: Any)and update the storyboard to use this action, this method DOES get called (note that it is called undo2)My application is not document based, and the structure is:WindowController -> Window -> ViewController -> TableViewNot sure it if this is relevant, but the viewController implements cut, copy, paste and delete, and as such has a validateMenuItem(_:) implementation.
Post not yet marked as solved
Is it possbible to avoid any app from accessing certain domains?For context: I noticed many, many apps accessing google tracking domains as well as facebook domains.I don't want or need either, so I would like to avoid having these apps send info to Google and facebook. Or actually anything other than their owns servers (from which they could still send the info to Google and facebook, but I'm hoping that is not common practice (yet?) ).
Can I use the Compression framework to compress / archive multiple files into one file? Compression is not important, I just want to be able to create a single file that can be sent somewhere and extracted into a folder with those files.If not the Compression framework, what other options do I have? I prefer to use built-in libraries and frameworks to minimize dependencies in my project, and I am find with me writing a lot of code for this.
In my app, I aimplemented a class that is initialized with an instance of UIView. After initialization, this instance is then exposed as a property on the class.After initializing this class, I would like the call site to be able to call functions on that exposed property based on the actual type. E.g. if it is a label, the callsite should be able to set a title, if it is a button, it should be able to call -setTitleColor:forState:, etc)Here is the current implementation of my class:class EmbeddingView: UIView
{
let embeddedView: UIView
let insets: UIEdgeInsets
required init(embeddedView: UIView,
insets: UIEdgeInsets)
{
self.insets = insets
self.embeddedView = embeddedView
super.init(frame: CGRect.zero)
self.addSubview(embeddedView)
// setup constraints on the embeddedView using the insets
// ...
}
required init?(coder aDecoder: NSCoder)
{
fatalError("init(coder:) has not been implemented")
}
}At the call site, I would like to be able to do things like this:let embeddedLabel = EmbeddingView(embeddedView: UILabel(), insets: UIEdgeInsets.zero)
embeddedLabel.embeddedView.title = "42"
let embeddedButton = EmbeddingView(embeddedView: UIButton(type: .system), insets: UIEdgeInsets.zero)
embeddedLabel.embeddedView.setTitleColor(UIColor.blue, for: .normal)So I changed the implementation to use generics, (indicated the places where I made changes, lines 1, 3 and 5)class EmbeddingView <T: UIView>: UIView //// note the addition here
{
let embeddedView: T // note the change here
let insets: UIEdgeInsets
required init(embeddedView: T, // note the change here
insets: UIEdgeInsets)
{
self.insets = insets
self.embeddedView = embeddedView
super.init(frame: CGRect.zero)
self.addSubview(embeddedView)
// setup constraints on the embeddedView
// ...
}
required init?(coder aDecoder: NSCoder)
{
fatalError("init(coder:) has not been implemented")
}
}Yet, at the call sites, I get compilation errors saying:"Value of type 'UILabel' has no member 'title'" and"Value of type 'UILabel' has no member 'setTitleColor'"How can I get the compiler to understand what is the real type of the instance that the EmbeddingView was initialized with?
Post not yet marked as solved
While trying to implement a Today Widget I was having some problems with resizing the widget. To figure out what is going on, I created a bare bones test project.Here is my understanding of how it is supposed to work:- indicate that the extension supports the .expanded mode (which causes the system to enable the 'Show More' / 'Show Less' toggle button) by adding the following code to -viewDidLoad:self.extensionContext.widgetLargestAvailableDisplayMode = NCWidgetDisplayModeExpanded;- setup the viewHierarchy using autolayout, so no need to set preferredContentSize on the viewController.- when the user taps the 'Show more' / /Show Less' button, the following method is called and in this method I can adjust my interface (e.g. remove / add views, change constraints etc):-(void)widgetActiveDisplayModeDidChange: (NCWidgetDisplayMode)activeDisplayMode withMaximumSize: (CGSize)maxSize- to animate the change in view size, I can implement the following method:-(void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinatorBased on these assuptions, I set up my widget to have a single view. It has constraints set up to pin top, bottom, leading and trailing to the viewController's view and layoutguides. There is also a height-constraint (set to 250) that is activated in .expanded mode.The behavior I expect is that the when the user taps 'Show more' for the first time, the widget expands. To achieve this, in -widgetActiveDisplayModeDidChange:withMaximumSize: I activate the height constraint. This should lead to -viewWillTransitionToSize:withTransitionCoordinator: being called where I can have the change animated.When the user tap 'Show Less' I expect the same sequence: -widgetActiveDisplayModeDidChange:withMaximumSize: is called, I inactivate the height-constraint and in -viewWillTransitionToSize:withTransitionCoordinator: I animate the changes again.The observed behavior is that the widget does indeed expand the first time the 'Show more' button is tapped. The first time the 'Show Less' button is tapped the widget shrinks back. However, when tapping on 'Show more' again, the widget does not expand, but the button switches to say 'Show Less' nonetheless.-widgetActiveDisplayModeDidChange:withMaximumSize: is still being called with the correct more, however, activating the constraint gives me some autolayout errors in the console now. -viewWillTransitionToSize:withTransitionCoordinator: is not called. (console output at the end of this post EDIT: removed the console edit as it marked this post for moderation).Remarkably, when -widgetActiveDisplayModeDidChange:withMaximumSize: is called with a mode of .expanded and I not only activate the height constraint, but set the constant property to maxSize.height, the 'Show more' / 'Show less' button results in the perfect behavior (except for the fact that I don't want my widget to be that large).You can find the full code below. Note that I do not use a storyboard, but create the view hierarchy in code (done in -viewDidLoad in this example).This seems very much like a bug to me, but I am hoping that I simply missed some documentation somewhere. Any suggestions?EDIT: radar 34082619#import "TodayViewController.h"
#import <NotificationCenter/NotificationCenter.h>
@interface TodayViewController () <NCWidgetProviding>
@property (nonatomic, strong) NSLayoutConstraint * heigthConstraint;
@end
@implementation TodayViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.extensionContext.widgetLargestAvailableDisplayMode = NCWidgetDisplayModeExpanded;
UIView * testView = [[UIView alloc] init];
testView.translatesAutoresizingMaskIntoConstraints = NO;
testView.backgroundColor = [UIColor greenColor];
testView.layer.borderColor = [UIColor blueColor].CGColor;
testView.layer.borderWidth = 1;
[self.view addSubview: testView];
NSLayoutConstraint * testViewTop =
[NSLayoutConstraint constraintWithItem: testView
attribute: NSLayoutAttributeTop
relatedBy: NSLayoutRelationEqual
toItem: self.topLayoutGuide
attribute: NSLayoutAttributeBottom
multiplier: 1.0
constant: 4];
NSLayoutConstraint * testViewLeading =
[NSLayoutConstraint constraintWithItem: testView
attribute: NSLayoutAttributeLeading
relatedBy: NSLayoutRelationEqual
toItem: self.view
attribute: NSLayoutAttributeLeading
multiplier: 1.0
constant: 4];
NSLayoutConstraint * testViewTrailing =
[NSLayoutConstraint constraintWithItem: testView
attribute: NSLayoutAttributeTrailing
relatedBy: NSLayoutRelationEqual
toItem: self.view
attribute: NSLayoutAttributeTrailing
multiplier: 1.0
constant: -4];
NSLayoutConstraint * testViewBottom =
[NSLayoutConstraint constraintWithItem: testView
attribute: NSLayoutAttributeBottom
relatedBy: NSLayoutRelationEqual
toItem: self.bottomLayoutGuide
attribute: NSLayoutAttributeTop
multiplier: 1.0
constant: -4];
self.heigthConstraint =
[NSLayoutConstraint constraintWithItem: testView
attribute: NSLayoutAttributeHeight
relatedBy: NSLayoutRelationEqual
toItem: nil
attribute: NSLayoutAttributeNotAnAttribute
multiplier: 1.0
constant: 400];
[self.view addConstraints: @[testViewBottom, testViewTop,
testViewTrailing, testViewLeading]];
}
- (void)widgetPerformUpdateWithCompletionHandler:(void (^)(NCUpdateResult))completionHandler
{
completionHandler(NCUpdateResultNewData);
}
-(void)widgetActiveDisplayModeDidChange: (NCWidgetDisplayMode)activeDisplayMode withMaximumSize: (CGSize)maxSize
{
switch (activeDisplayMode)
{
case NCWidgetDisplayModeCompact:
self.heigthConstraint.active = NO;
break;
case NCWidgetDisplayModeExpanded:
/
self.heigthConstraint.constant = 250; break;
}
}
-(void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
{
[coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context)
{
[self.view layoutIfNeeded];
} completion: NULL];
}
@end
Post not yet marked as solved
While trying to implement a Today Widget I was having some problems with resizing the widget. To figure out what is going on, I created a bare bones test project.Here is my understanding of how it is supposed to work:- indicate that the extension supports the .expanded mode (which causes the system to enable the 'Show More' / 'Show Less' toggle button) by adding the following code to -viewDidLoad:self.extensionContext.widgetLargestAvailableDisplayMode = NCWidgetDisplayModeExpanded;- setup the viewHierarchy using autolayout, so no need to set preferredContentSize on the viewController.- when the user taps the 'Show more' / /Show Less' button, the following method is called and in this method I can adjust my interface (e.g. remove / add views, change constraints etc):-(void)widgetActiveDisplayModeDidChange: (NCWidgetDisplayMode)activeDisplayMode withMaximumSize: (CGSize)maxSize- to animate the change in view size, I can implement the following method:-(void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinatorBased on these assuptions, I set up my widget to have a single view. It has constraints set up to pin top, bottom, leading and trailing to the viewController's view and layoutguides. There is also a height-constraint (set to 250) that is activated in .expanded mode.The behavior I expect is that the when the user taps 'Show more' for the first time, the widget expands. To achieve this, in -widgetActiveDisplayModeDidChange:withMaximumSize: I activate the height constraint. This should lead to -viewWillTransitionToSize:withTransitionCoordinator: being called where I can have the change animated.When the user tap 'Show Less' I expect the same sequence: -widgetActiveDisplayModeDidChange:withMaximumSize: is called, I inactivate the height-constraint and in -viewWillTransitionToSize:withTransitionCoordinator: I animate the changes again.The observed behavior is that the widget does indeed expand the first time the 'Show more' button is tapped. The first time the 'Show Less' button is tapped the widget shrinks back. However, when tapping on 'Show more' again, the widget does not expand, but the button switches to say 'Show Less' nonetheless.-widgetActiveDisplayModeDidChange:withMaximumSize: is still being called with the correct more, however, activating the constraint gives me some autolayout errors in the console now. -viewWillTransitionToSize:withTransitionCoordinator: is not called. (console output at the end of this post).Remarkably, when -widgetActiveDisplayModeDidChange:withMaximumSize: is called with a mode of .expanded and I not only activate the height constraint, but set the constant property to maxSize.height, the 'Show more' / 'Show less' button results in the perfect behavior (except for the fact that I don't want my widget to be that large).You can find the full code below. Note that I do not use a storyboard, but create the view hierarchy in code (done in -viewDidLoad in this example).This seems very much like a bug to me, but I am hoping that I simply missed some documentation somewhere. Any suggestions?#import "TodayViewController.h"
#import <NotificationCenter/NotificationCenter.h>
@interface TodayViewController () <NCWidgetProviding>
@property (nonatomic, strong) NSLayoutConstraint * heigthConstraint;
@end
@implementation TodayViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.extensionContext.widgetLargestAvailableDisplayMode = NCWidgetDisplayModeExpanded;
UIView * testView = [[UIView alloc] init];
testView.translatesAutoresizingMaskIntoConstraints = NO;
testView.backgroundColor = [UIColor greenColor];
testView.layer.borderColor = [UIColor blueColor].CGColor;
testView.layer.borderWidth = 1;
[self.view addSubview: testView];
NSLayoutConstraint * testViewTop =
[NSLayoutConstraint constraintWithItem: testView
attribute: NSLayoutAttributeTop
relatedBy: NSLayoutRelationEqual
toItem: self.topLayoutGuide
attribute: NSLayoutAttributeBottom
multiplier: 1.0
constant: 4];
NSLayoutConstraint * testViewLeading =
[NSLayoutConstraint constraintWithItem: testView
attribute: NSLayoutAttributeLeading
relatedBy: NSLayoutRelationEqual
toItem: self.view
attribute: NSLayoutAttributeLeading
multiplier: 1.0
constant: 4];
NSLayoutConstraint * testViewTrailing =
[NSLayoutConstraint constraintWithItem: testView
attribute: NSLayoutAttributeTrailing
relatedBy: NSLayoutRelationEqual
toItem: self.view
attribute: NSLayoutAttributeTrailing
multiplier: 1.0
constant: -4];
NSLayoutConstraint * testViewBottom =
[NSLayoutConstraint constraintWithItem: testView
attribute: NSLayoutAttributeBottom
relatedBy: NSLayoutRelationEqual
toItem: self.bottomLayoutGuide
attribute: NSLayoutAttributeTop
multiplier: 1.0
constant: -4];
self.heigthConstraint =
[NSLayoutConstraint constraintWithItem: testView
attribute: NSLayoutAttributeHeight
relatedBy: NSLayoutRelationEqual
toItem: nil
attribute: NSLayoutAttributeNotAnAttribute
multiplier: 1.0
constant: 400];
[self.view addConstraints: @[testViewBottom, testViewTop,
testViewTrailing, testViewLeading]];
}
- (void)widgetPerformUpdateWithCompletionHandler:(void (^)(NCUpdateResult))completionHandler
{
completionHandler(NCUpdateResultNewData);
}
-(void)widgetActiveDisplayModeDidChange: (NCWidgetDisplayMode)activeDisplayMode withMaximumSize: (CGSize)maxSize
{
switch (activeDisplayMode)
{
case NCWidgetDisplayModeCompact:
self.heigthConstraint.active = NO;
break;
case NCWidgetDisplayModeExpanded:
/
self.heigthConstraint.constant = 250;
self.heigthConstraint.active = YES;
break;
}
}
-(void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
{
[coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context)
{
[self.view layoutIfNeeded];
} completion: NULL];
}
@endConsole output:TodayTestWidget[26437:1682768] [LayoutConstraints] Unable to simultaneously satisfy constraints. Probably at least one of the constraints in the following list is one you don't want. Try this: (1) look at each constraint and try to figure out which you don't expect; (2) find the code that added the unwanted constraint or constraints and fix it. ( "<_UILayoutSupportConstraint:0x608000286bd0 _UILayoutSpacer:0x6080003a1260'UIVC-topLayoutGuide'.height == 0 (active)>", "<_UILayoutSupportConstraint:0x608000286ae0 V:|-(0)-[_UILayoutSpacer:0x6080003a1260'UIVC-topLayoutGuide'] (active, names: '|':UIView:0x7fa0fe40b8a0 )>", "<_UILayoutSupportConstraint:0x608000287990 _UILayoutSpacer:0x6080001bff00'UIVC-bottomLayoutGuide'.height == 0 (active)>", "<_UILayoutSupportConstraint:0x608000287940 _UILayoutSpacer:0x6080001bff00'UIVC-bottomLayoutGuide'.bottom == UIView:0x7fa0fe40b8a0.bottom (active)>", "<NSLayoutConstraint:0x608000287a30 UIView:0x7fa0fe40ba40.bottom == _UILayoutSpacer:0x6080001bff00'UIVC-bottomLayoutGuide'.top - 4 (active)>", "<NSLayoutConstraint:0x608000287710 V:[_UILayoutSpacer:0x6080003a1260'UIVC-topLayoutGuide']-(4)-[UIView:0x7fa0fe40ba40] (active)>", "<NSLayoutConstraint:0x608000287a80 UIView:0x7fa0fe40ba40.height == 250 (active)>", "<NSLayoutConstraint:0x608000283980 'UIView-Encapsulated-Layout-Height' UIView:0x7fa0fe40b8a0.height == 110 (active)>")Will attempt to recover by breaking constraint <NSLayoutConstraint:0x608000287a80 UIView:0x7fa0fe40ba40.height == 250 (active)>Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKit/UIView.h> may also be helpful.
Post not yet marked as solved
It seems I do not understand correctly how the UTI and UIDocumentBrowserViewController are related. When I modify the Info.plist's 'CFBundleDocumentTypes' key, it seems that the following function no longer gets called when tapping the '+' button:func documentBrowser(_ controller: UIDocumentBrowserViewController, didRequestDocumentCreationWithHandler importHandler: @escaping (URL?, UIDocumentBrowserViewController.ImportMode) -> Void) 'Specifically, I am using the Document Based App template, made no changes to the code, only changed 'public.image' to 'com.mycompany.mytype' as the first item in the 'LSItemContentTypes' array.So it seems I am in need of some documentation about how these keys affect the UIDocumentBrowserViewController, both in my app and in other 3rd party apps. I read the following, but it was of no help:https://developer.apple.com/library/content/documentation/FileManagement/Conceptual/DocumentInteraction_TopicsForIOS/Articles/RegisteringtheFileTypesYourAppSupports.htmlIs there proper documentation on exactly how this works and how to set up a project?(As a side-problem I would like to use UIManagedDocument, but exactly how to set up a document based app using UIManagedDocument is also unclear. WWDC sessions seem to become more and more superficial each year, so those are unfortunately not of any help.)