Adding Constraints Programmatically on iOS - autolayout

So I'm trying to get the first and third button to take up the rest of the space of the tab bar and have the second button be a constant width. I'm getting close but I can't seem to quite get it. This is my first time adding constraints programmatically so I welcome constructive criticism. Here is the code. On the image each color is a button.
- (void)setUpTabbarProperties {
[self.tabBar setHidden: YES];
[self.customTabBarView setTranslatesAutoresizingMaskIntoConstraints: NO];
[self.firstButton setTranslatesAutoresizingMaskIntoConstraints: NO];
[self.secondButton setTranslatesAutoresizingMaskIntoConstraints: NO];
[self.thirdButton setTranslatesAutoresizingMaskIntoConstraints: NO];
[self.view addSubview: self.customTabBarView];
// This will add the constraints
NSDictionary *viewDictionary = [NSDictionary dictionaryWithObjectsAndKeys: self.customTabBarView, #"customTabBar",
self.firstButton, #"firstButton",
self.secondButton, #"secondButton",
self.thirdButton, #"thirdButton", nil];
NSArray *horizontalConstraints = [NSLayoutConstraint constraintsWithVisualFormat: #"H:|-0-[customTabBar]-0-|"
options: 0
metrics: nil
views: viewDictionary];
NSArray *verticalConstraints = [NSLayoutConstraint constraintsWithVisualFormat: #"V:[customTabBar]-0-|"
options: 0
metrics: nil
views: viewDictionary];
NSArray *heightContraints = [NSLayoutConstraint constraintsWithVisualFormat: #"V:[customTabBar(50)]"
options: 0
metrics: nil
views: viewDictionary];
NSArray *buttonHConstraints = [NSLayoutConstraint constraintsWithVisualFormat: #"H:|-0-[firstButton]-0-[secondButton]-0-[thirdButton]-0-|"
options: 0
metrics: nil
views: viewDictionary];
NSArray *firstButtonVConstraints = [NSLayoutConstraint constraintsWithVisualFormat: #"V:|-0-[firstButton]-0-|"
options: 0
metrics: nil
views: viewDictionary];
NSArray *secondButtonVConstraints = [NSLayoutConstraint constraintsWithVisualFormat: #"V:|-0-[secondButton]-0-|"
options: 0
metrics: nil
views: viewDictionary];
NSArray *thirdButtonVConstraints = [NSLayoutConstraint constraintsWithVisualFormat: #"V:|-0-[thirdButton]-0-|"
options: 0
metrics: nil
views: viewDictionary];
NSArray *secondButtonWithConstraint = [NSLayoutConstraint constraintsWithVisualFormat: #"H:[secondButton(65)]"
options: 0
metrics: nil
views: viewDictionary];
[self.view addConstraint: [NSLayoutConstraint constraintWithItem: self.secondButton
attribute: NSLayoutAttributeCenterY
relatedBy: NSLayoutRelationEqual
toItem: self.customTabBarView
attribute: NSLayoutAttributeCenterY
multiplier: 0
constant: 0]];
[self.view addConstraints: horizontalConstraints];
[self.view addConstraints: verticalConstraints];
[self.view addConstraints: heightContraints];
[self.view addConstraints: buttonHConstraints];
[self.view addConstraints: firstButtonVConstraints];
[self.view addConstraints: secondButtonVConstraints];
[self.view addConstraints: thirdButtonVConstraints];
[self.view addConstraints: secondButtonWithConstraint];
}
and here what it looks like

I was missing the constraint. I was trying to use a different one and it didn't work but this one did.
NSArray *firstAndThirdButtonConstraint = [NSLayoutConstraint constraintsWithVisualFormat:#"H:[firstButton(==thirdButton)]"
options:0
metrics:nil views:viewDictionary];

Related

App crashed on iOS 13, after switching rootviewcontroller

Application runs fine on iOS 12. But crashed on iOS 13.1. I think the issue is in the setting root view controller.
let storyboard = UIStoryboard(name: storyBoard, bundle: nil)
let desiredViewController = storyboard.instantiateViewController(withIdentifier: identifier);
self.window?.switchRootViewController(desiredViewController,animated: animate,options:options)
and the extension is:
extension UIWindow {
func switchRootViewController(_ viewController: UIViewController, animated: Bool = true, duration: TimeInterval = 0.3, options: UIView.AnimationOptions = .transitionFlipFromRight, completion: (() -> Void)? = nil) {
guard animated else {
rootViewController = viewController
return
}
UIView.transition(with: self, duration: duration, options: options, animations: {
let oldState = UIView.areAnimationsEnabled
UIView.setAnimationsEnabled(false)
self.rootViewController = viewController
UIView.setAnimationsEnabled(oldState)
}) { _ in
completion?()
}
}
}

Composing Text Providers with CLKTextProvider.localizableTextProvider(withStringsFileFormatKey:, textProviders:)

Are there any official examples of setting up a complication using localizableTextProvider(withStringsFileFormatKey:, textProviders:)? I can get the text provider to populate when producing a SampleTemplate, but whenever I try to generate a template using getTimelineEntries the text provider generated by localizableTextProvider the result is always blank, no text.
Example (only supporting .utilitarianLarge):
func getCurrentTimelineEntry(for complication: CLKComplication, withHandler handler: #escaping (CLKComplicationTimelineEntry?) -> Void) {
// Call the handler with the current timeline entry
let template = CLKComplicationTemplateUtilitarianLargeFlat()
template.textProvider = CLKTextProvider.localizableTextProvider(
withStringsFileFormatKey: "testComplication",
textProviders: [
CLKSimpleTextProvider(text: "Hello"),
CLKSimpleTextProvider(text: "World")
]
)
handler(CLKComplicationTimelineEntry(date: Date(), complicationTemplate: template))
}
and sampleTemplate as
func getLocalizableSampleTemplate(for complication: CLKComplication, withHandler handler: #escaping (CLKComplicationTemplate?) -> Void) {
// This method will be called once per supported complication, and the results will be cached
switch complication.family {
case .utilitarianLarge:
let template = CLKComplicationTemplateUtilitarianLargeFlat()
template.textProvider = CLKTextProvider.localizableTextProvider(
withStringsFileFormatKey: "testComplication",
textProviders: [
CLKSimpleTextProvider(text: "Hi"),
CLKSimpleTextProvider(text: "World")
]
)
handler(template)
default:
handler(nil)
}
}
with ckcomplication.strings as
"testComplication" = "%# %#";
The sample template will always display the text "Hi World", whereas the result from getCurrentTimelineEntry will always display an empty complication.
Has anyone had luck composing text providers in this way?
It appears this API is just broken in Swift (as of 4.2). I worked around this with an Objective C category. Blatantly stolen from here
CLKTextProvider+NNNCompoundTextProviding.h
#interface CLKTextProvider (NNNCompoundTextProviding)
+ (nonnull CLKTextProvider *)nnn_textProviderByJoiningProvider:(nonnull CLKTextProvider *)provider1 andProvider:(nonnull CLKTextProvider *)provider2 withString:(nullable NSString *)joinString;
#end
CLKTextProvider+NNNCompoundTextProviding.m
#implementation CLKTextProvider (NNNCompoundTextProviding)
+ (nonnull CLKTextProvider *)nnn_textProviderByJoiningProvider:(nonnull CLKTextProvider *)provider1 andProvider:(nonnull CLKTextProvider *)provider2 withString:(nullable NSString *)joinString
{
NSString *textProviderToken = #"%#";
NSString *formatString;
if (joinString != nil) {
formatString = [NSString stringWithFormat:#"%#%#%#",
textProviderToken,
joinString,
textProviderToken];
}
else {
formatString = [NSString stringWithFormat:#"%#%#",
textProviderToken,
textProviderToken];
}
return [self textProviderWithFormat:formatString, provider1, provider2];
}
#end

UIIMagePickerController not working properly with Swift 3

I'm getting crazy with this.
While migrating a simple UIIMagePicker implementation I have everything working properly , no issues, no warning.
Running on both simulator and device the PickerController just don't take into consideration the new image and won't apply to the UImageView.
This is my code:
#IBOutlet weak var profilePictureImageView: UIImageView!
let picker = UIImagePickerController()
override func viewDidLoad() {
super.viewDidLoad()
picker.delegate = self
}
//Opening Camera to take a new picture
#IBAction func takeNewPictureButtonDidTouch(_ sender: AnyObject) {
if UIImagePickerController.isSourceTypeAvailable(.camera) {
picker.allowsEditing = false
picker.sourceType = UIImagePickerControllerSourceType.camera
picker.cameraCaptureMode = .photo
picker.modalPresentationStyle = .fullScreen
present(picker,animated: true,completion: nil)
} else {
noCamera()
}
}
//Opening the library to select an existing picture
#IBAction func openPicturesLibraryButtonDidTouch(_ sender: AnyObject) {
picker.allowsEditing = false
picker.sourceType = .photoLibrary
picker.mediaTypes = UIImagePickerController.availableMediaTypes(for: .photoLibrary)!
picker.modalPresentationStyle = .popover
present(picker, animated: true, completion: nil)
}
//MARK: - Delegates
func imagePickerController(_ picker: UIImagePickerController,
didFinishPickingMediaWithInfo info: [String : AnyObject])
{
var chosenImage = UIImage()
chosenImage = info[UIImagePickerControllerOriginalImage] as! UIImage //2
profilePictureImageView.contentMode = .scaleAspectFill //3
profilePictureImageView.image = chosenImage //4
dismiss(animated:true, completion: nil) //5
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
dismiss(animated: true, completion: nil)
}
func noCamera(){
let alertVC = UIAlertController(
title: "No Camera",
message: "Sorry, this device has no camera",
preferredStyle: .alert)
let okAction = UIAlertAction(
title: "OK",
style:.default,
handler: nil)
alertVC.addAction(okAction)
present(
alertVC,
animated: true,
completion: nil)
}
}
set delegate to self in viewdidload

Using Autolayout Visual Format with Swift?

I've been trying to use the Autolayout Visual Format Language in Swift, using NSLayoutConstraint.constraintsWithVisualFormat. Here's an example of some code that does nothing useful, but as far as I can tell should make the type checker happy:
let foo:[AnyObject]! = NSLayoutConstraint.constraintsWithVisualFormat(
format: "", options: 0, metrics: {}, views: {})
However, this triggers the compiler error:
"Cannot convert the expression's type '[AnyObject]!' to type 'String!'".
Before I assume this is a Radar-worthy bug, is there anything obvious I'm missing here? This happens even without the explicit casting of the variable name, or with other gratuitous downcasting using as. I can't see any reason why the compiler would be expecting any part of this to resolve to a String!.
this works for me with no error:
let bar:[AnyObject]! = NSLayoutConstraint.constraintsWithVisualFormat(
nil, options: NSLayoutFormatOptions(0), metrics: nil, views: nil)
update
the line above may not be compiled since the 1st and 4th parameters cannot be optionals anymore.
syntactically those have to be set, like e.g. this:
let bar:[AnyObject] = NSLayoutConstraint.constraintsWithVisualFormat("", options: NSLayoutFormatOptions(0), metrics: nil, views: ["": self.view])
update
(for Xcode 7, Swift 2.0)
the valid syntax now requests the parameters's name as well, like:
NSLayoutFormatOptions(rawValue: 0)
NOTE: this line of code shows the correct syntax only, the parameters itself won't guarantee the constraint will be correct or even valid!
The first gotcha here is that Swift Dictionary is not yet bridged with NSDictionary. To get this to work, you'll want to explicitly create a NSDictionary for each NSDictionary-typed parameters.
Also, as Spencer Hall points out, {} isn't a dictionary literal in Swift. The empty dictionary is written:
[:]
As of XCode 6 Beta 2, this solution allows you to create constraints with the visual format:
var viewBindingsDict: NSMutableDictionary = NSMutableDictionary()
viewBindingsDict.setValue(fooView, forKey: "fooView")
viewBindingsDict.setValue(barView, forKey: "barView")
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-[fooView]-[barView]-|", options: nil, metrics: nil, views: viewBindingsDict))
Try this - remove the initial variable name (format:), use NSLayoutFormatOptions(0), and just pass nil for those optional NSDictionaries:
let foo:[AnyObject]! = NSLayoutConstraint.constraintsWithVisualFormat("", options: NSLayoutFormatOptions(0), metrics: nil, views: nil)
FYI:
if you use views with constraintWithVisualFormat - instead of wrapping with NSMutableDict
["myLabel": self.myLabel!]
and to be more specific
var constraints = [NSLayoutConstraint]()
NSLayoutConstraint.constraintsWithVisualFormat("H:|-15-[myLabel]-15-|",
options:NSLayoutFormatOptions.allZeros,
metrics: nil,
views: ["myLabel": self.myLabel!]).map {
constraints.append($0 as NSLayoutConstraint)
}
This works with Xcode 6.1.1:
extension NSView {
func addConstraints(constraintsVFL: String, views: [String: NSView], metrics: [NSObject: AnyObject]? = nil, options: NSLayoutFormatOptions = NSLayoutFormatOptions.allZeros) {
let mutableDict = (views as NSDictionary).mutableCopy() as NSMutableDictionary
let constraints = NSLayoutConstraint.constraintsWithVisualFormat(constraintsVFL, options: options, metrics: metrics, views: mutableDict)
self.addConstraints(constraints)
}
}
Then you can use calls like:
var views : [String: NSView] = ["box": self.box]
self.view.addConstraints("V:[box(100)]", views: views)
This works to add constraints. If you are using iOS, substitute UIView for NSView
You should probably check out
Cartography, which is a new approach, but quite awesome. It uses Autolayout under the hood.
SnapKit, which I haven't tried but is also a DSL autolayout framework
NSLayoutFormatOptions implements the OptionSetType protocol, which inherits from SetAlgebraType which inherits from ArrayLiteralConvertible, so you can initialise NSLayoutFormatOptions like this: [] or this: [.DirectionLeftToRight, .AlignAllTop]
So, you can create the layout constraints like this:
NSLayoutConstraint.constraintsWithVisualFormat("", options: [], metrics: nil, views: [:])
It slightly annoys me that I'm calling NSLayoutConstraint (singular) to generate constraintsWithVisualFormat... (plural), though I'm sure that's just me. In any case, I have these two top level functions:
snippet 1 (Swift 1.2)
#if os(iOS)
public typealias View = UIView
#elseif os(OSX)
public typealias View = NSView
#endif
public func NSLayoutConstraints(visualFormat: String, options: NSLayoutFormatOptions = .allZeros, views: View...) -> [NSLayoutConstraint] {
return NSLayoutConstraints(visualFormat, options: options, views: views)
}
public func NSLayoutConstraints(visualFormat: String, options: NSLayoutFormatOptions = .allZeros, views: [View] = []) -> [NSLayoutConstraint] {
if visualFormat.hasPrefix("B:") {
let h = NSLayoutConstraints("H\(dropFirst(visualFormat))", options: options, views: views)
let v = NSLayoutConstraints("V\(dropFirst(visualFormat))", options: options, views: views)
return h + v
}
var dict: [String:View] = [:]
for (i, v) in enumerate(views) {
dict["v\(i + 1)"] = v
}
let format = visualFormat.stringByReplacingOccurrencesOfString("[v]", withString: "[v1]")
return NSLayoutConstraint.constraintsWithVisualFormat(format, options: options, metrics: nil, views: dict) as! [NSLayoutConstraint]
}
Which can be used like so:
superView.addConstraints(NSLayoutConstraints("B:|[v]|", view))
In other words, views are auto-named "v1" to "v\(views.count)" (except the first view which can be also referred to as "v"). In addition, prefixing the format with "B:" will generate both the "H:" and "V:" constraints. The example line of code above therefore means, "make sure the view always fits the superView".
And with the following extensions:
snippet 2
public extension View {
// useMask of nil will not affect the views' translatesAutoresizingMaskIntoConstraints
public func addConstraints(visualFormat: String, options: NSLayoutFormatOptions = .allZeros, useMask: Bool? = false, views: View...) {
if let useMask = useMask {
for view in views {
#if os(iOS)
view.setTranslatesAutoresizingMaskIntoConstraints(useMask)
#elseif os(OSX)
view.translatesAutoresizingMaskIntoConstraints = useMask
#endif
}
}
addConstraints(NSLayoutConstraints(visualFormat, options: options, views: views))
}
public func addSubview(view: View, constraints: String, options: NSLayoutFormatOptions = .allZeros, useMask: Bool? = false) {
addSubview(view)
addConstraints(constraints, options: options, useMask: useMask, views: view)
}
}
We can do some common tasks much more elegantly, like adding a button at a standard offset from the bottom right corner:
superView.addSubview(button, constraints: "B:[v]-|")
For example, in an iOS playground:
import UIKit
import XCPlayground
// paste here `snippet 1` and `snippet 2`
let view = UIView(frame: CGRect(x: 0, y: 0, width: 500, height: 500))
XCPShowView("view", view)
view.backgroundColor = .orangeColor()
XCPShowView("view", view)
let button = UIButton(frame: CGRect(x: 0, y: 0, width: 50, height: 50))
button.setTitle("bottom right", forState: .Normal)
view.addSubview(button, constraints: "B:[v]-|")
You have to access to the struct NSLayoutFormatOptions.
Following works for me.
self.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("",
options:NSLayoutFormatOptions.AlignAllBaseline,
metrics: nil, views: nil))
// topLayoutGuide constraint
var views: NSMutableDictionary = NSMutableDictionary()
views.setValue(taskNameField, forKey: "taskNameField")
views.setValue(self.topLayoutGuide, forKey: "topLayoutGuide")
let verticalConstraint = "V:[topLayoutGuide]-20-[taskNameField]"
let constraints:[AnyObject]! = NSLayoutConstraint.constraintsWithVisualFormat(verticalConstraint, options: NSLayoutFormatOptions(0), metrics: nil, views: views)
self.view.addConstraints(constraints)
// bottomLayoutGuide constraint
var views: NSMutableDictionary = NSMutableDictionary()
views.setValue(logoutButton, forKey: "logoutButton")
views.setValue(self.bottomLayoutGuide, forKey: "bottomLayoutGuide")
let verticalConstraint = "V:[logoutButton]-20-[bottomLayoutGuide]"
let constraints:[AnyObject]! = NSLayoutConstraint.constraintsWithVisualFormat(verticalConstraint, options: NSLayoutFormatOptions(0), metrics: nil, views: views)
self.view.addConstraints(constraints)

FOSElasticaBundle multiple nested query

I use FOSElasticaBundle to handle searching. All works great when I have one level of nesting. However, when I have two levels of nesting results which should match the innermost nest are not returned (e.g. searching for 'xx' category does produce results, but searching for 'yy' brand does not - and should).
Here's my fos_elastica configuration:
fos_elastica:
clients:
default: { host: localhost, port: 9200 }
indexes:
my_index:
client: default
types:
product:
mappings:
title: { boost: 1 }
articleNumber: ~
introductionDateSearch: { type: integer }
delistingDateSearch: { type: integer }
deleted: { type: boolean }
category:
type: "nested"
properties:
name: { boost: 1 }
brand:
type: "nested"
properties:
name: { boost: 1 }
persistence:
driver: orm
model: MyBundle\Entity\Product
provider: ~
finder: ~
listener: ~
And my query handler:
public function searchForKeyword($keyword, AbstractUser $user)
{
$this->setFilters($user);
$keyword = trim($keyword);
if ($keyword !== '') {
$mainQuery = new \Elastica\Query\Bool();
$mainProductQuery = new \Elastica\Query\Bool();
//searching in Product title
$productQuery = new \Elastica\Query\Text();
$productQuery->setFieldQuery('title', $keyword);
$productQuery->setFieldParam('title', 'boost', 5);
$productQuery->setFieldParam('title', 'type', 'phrase_prefix');
//searching in Product articleNumber
$articleNumberQuery = new \Elastica\Query\Text();
$articleNumberQuery->setFieldQuery('articleNumber', $keyword);
$articleNumberQuery->setFieldParam('articleNumber', 'boost', 5);
$articleNumberQuery->setFieldParam('articleNumber', 'type', 'phrase_prefix');
//searching in Category name
$categoryQuery = new \Elastica\Query\Text();
$categoryQuery->setFieldQuery('name', $keyword);
$categoryQuery->setFieldParam('name', 'boost', 3);
$categoryQuery->setFieldParam('name', 'type', 'phrase_prefix');
$nestedCategoryProductQuery = new \Elastica\Query\Nested();
$nestedCategoryProductQuery->setPath('category');
$nestedCategoryProductQuery->setQuery($categoryQuery);
//searching in Brand name
$brandQuery = new \Elastica\Query\Text();
$brandQuery->setFieldQuery('name', $keyword);
$brandQuery->setFieldParam('name', 'boost', 3);
$brandQuery->setFieldParam('name', 'type', 'phrase_prefix');
$nestedBrandCategoryQuery = new \Elastica\Query\Nested();
$nestedBrandCategoryQuery->setPath('category.brand');
$nestedBrandCategoryQuery->setQuery($brandQuery);
$mainProductQuery->addShould($productQuery);
$mainProductQuery->addShould($articleNumberQuery);
$mainProductQuery->addShould($nestedCategoryProductQuery);
$mainProductQuery->addShould($nestedBrandCategoryQuery);
$mainQuery->addMust($mainProductQuery);
$esFilteredQuery = new \Elastica\Query\Filtered($mainQuery, $this->filters);
} else {
$esFilteredQuery = new \Elastica\Query\Filtered(new \Elastica\Query\MatchAll(), $this->filters);
}
$this->query = new \Elastica\Query();
$this->query->setQuery($esFilteredQuery);
}
How is the $nestedBrandCategoryQuery added to the $mainProductQuery?
Thanks for your help!
gtb
FOSElasticaBundle uses the Elastica Library. So this should not be an issue of FOSElasticaBundle.
Have a Look at http://elastica.io/ for more Details about the Lib. In my experience,there is nothing you can not do with Elastica if it is supported by Elasticsearch. Even when there is no Mapper in Elastica, just use the Raw Array Query (http://elastica.io/example/raw-array-query.html) to build the desired Query.

Resources